This document discusses dependency injection (DI) in .NET applications. It defines DI as a design pattern that implements inversion of control to resolve dependencies. An example shows how to refactor legacy code to use DI by introducing interfaces and constructor injection. The benefits of DI include easier testing using mocks, loose coupling between classes, and centralized configuration. DI should be used across architectural and responsibility boundaries. Various strategies for implementing DI are discussed, including delegates, mock POCOs, and frameworks like Moq. Legacy code challenges around static methods and cookies are also addressed.
1. Dependency Injection with .NET
-OR-
So you’ve decided to finally inject
your dependencies
-OR-
Let’s test production scenarios so it
doesn’t break
2. Overview
• What is DI?
• Why use DI?
• Where to use DI?
• How to do DI?
• C#, Legacy Code, and DI
– Interfaces, and Singletons, and Mocks, oh my!
3. What is Dependency Injection
• A software design pattern that implements inversion of
control for resolving dependencies. A dependency is an
object that can be used (a service). An injection is the passing
of a dependency to a dependent object (a client) that would
use it.
– Wikipedia
AppIoC
Dependency
Concrete
Implementation
Concrete
Implementation
Concrete
Implementation
4. Dependency Inject – An Example
• Legacy code w/o DI
public class ReviewManager{
public ElasticSearchBackedReviewRepository ReviewsRepo
{get; set;}
public void GetReviewsByMovie( int movieId){
return
ElasticSearchBackedReviewRepository.GetReviews(movieId);
}
}
• Legacy code w/o DI
public class ReviewManager{
public IReviewRepository ReviewsRepo {get; set;}
Public ReviewManager( IReviewRepository repo ){
ReviewsRepo = repo;
}
public void GetReviewsByMovie( int movieId){
return ReviewsRepo.GetReviews(moviedId);
}
}
AppIoC
Dependency
Concrete
Implementation
Concrete
Implementation
Concrete
Implementation
5. Why Interfaces?
• Develop against a contract
– Business logic isn’t changed as new
implementations are introduced
• Moq (for unit tests) requires interfaces or
virtual methods.
• All wiring for dependencies can be performed
in a centralized location
6. Why Mocking?
• Allows testing any use case without the need
for test data
– Mock a response that returns the use case you are
testing (as opposed to test data in the DB)
– Null return values
– Timeout (web service clients)
– 0 reviews, 1 review, 10000 reviews
7. When? Architecture Boundaries
App (Front End)
App (Business Logic)
DB 1
DB 2
Service 1
Service 1
Service 1
Any time the app reaches out for data is a good candidate for dependency injection.
In this scenario, the clients we use to wrap the 2 databases and the 3 services
would each implement an interface that could be mocked for unit tests
8. When? Responsibility Boundaries
• Fandango Desktop/Mobile Web
– Reviews (Direct DB calls, Elastic search service)
– Movies (DB calls, movie service)
– Theaters (DB calls, Commerce service, old service,
new API)
9. DI via Delegates
• Initial Effort is low
– No extra frameworks
– unit test can define the mocked method
• Subsequent efforts are O(N)
– One change per new mocked method
• Pros
– Methods can be incorporated one at a time
• Cons
– Each new method will require its own delegate and
new wiring.
10. DI via Mock POCOs
• Initial Effort is medium
– New class for each mock
– Logic for mock states
• Subsequent efforts
– Potential for new forks (and bugs) in mock POCO logic for
each use case
• Pros
– Don’t need to learn a new framework.
• Cons
– Supporting all use cases increases potential for bugs in the
mocks.
– Some dependencies can’t be mocked
1
1 – With Moq, you can only mock methods defined in an interface or virtual methods.
11. DI with Mocks (Moq)
• Initial Effort is high
– Each unit test use case requires its own moq wrapper
per dependency
• Subsequent changes
– Only changes to the contract require modifications to
your mocks
• Pros
– Each tests clearly identifies its assumptions
• Cons
– Lots of unit test code will just be setting up the
dependency
12. Legacy Code: Static Methods
• Interfaces can’t have static methods
• Convert static methods to instance methods
that conform to the (new) interface with a
Singleton to expose the static instance.
13. Scenario
Production issue with unknown cause
• Scenario in production we can’t reproduce
easily
• In a unit test, mock the underlying interface to
the throw the same exception.
– At the very least, we’ll know how to stop this error
from crashing the entire app and set up proper
logging to identify potential causes
14. Scenario
Production issue with unknown cause
[Fact]
public void TestIndex_WebExceptionFromReviewManager()
{
InjectCookieCollectionDependency();
var mock = new Mock<IReviewManager>();
mock.Setup(rm => rm.GetEsReviewsByMovie(
It.IsAny<int>(),
It.IsAny<int>(),
It.IsAny<int>(),
It.IsAny<UserReviewSort>()))
.Throws(new WebException());
IReviewManager reviewManager = mock.Object;
using (var controller = new MovieController(reviewManager) { ControllerContext = new ControllerContext { HttpContext = new
MockHttpContext() } })
{
MockHttpContext httpContext = configureContext(UserAgentValue);
controller.ControllerContext.HttpContext = httpContext;
ActionResult result = controller.Index(MovieIdAmericanBeauty);
Assert.IsType(typeof (ViewResult), result);
}
}
15. Scenario
Anonymous + authenticated users
• Our apps identify a logged in user by the
presence of a cookie. We can mock that!
– See changes to MovieController with
ICustomerCookieProvider
16. Strategies for Unit Testing
• New Projects
– TDD allows us to work with QA to identify good
test cases before we start writing code
• Legacy Projects
– Don’t test something that’s been working in
production for years
– Create unit tests to reproduce your bug. This will
allow for immediate ROI on the tests
– Introduce tests for new features
17. Side Effect (Good)
• Enforces the SOLID principles
Single Responsibility [link]
If an interface crosses domains, you know to split them
Multiple interfaces too many responsibilities for the class
Open/Closed Principle [link]
Your code is inherently open to extension
Liskov Substitution Principle [link]
Your mocks provide are substituted for the concrete
implementations without code changes
Interface segregation Principle [link]
Interface contract hides helper methods
Dependency inversion principle [link]
This is what we’re talking about here
18. Further Reading
• Martin Fowler (http://martinfowler.com/articles/injection.html)
• AutoFac IoC container(http://autofac.org/)
• AutoFac in actionNuGet Gallery – DI by Environment