Dependency injection - the right way
- 3. This talk
• What it is about
– Dependency Injection (DI) patterns
– Benefits
– Common pitfalls
• What it is not about
– Specific IoC/DI Container implementations
• Pre-requisites
– OOP
– Class-based statically-typed languages
• Based on examples
- 7. Dependency Injection
Dependency Injection is a set of practices that
allow to build loosely coupled applications
It’s NOT :
– A library
– A framework
– A tool
It IS :
- A way of thinking
- A way of designing code
- General guidelines
- 8. Dependency Injection
Dependency Injection is a set of practices that
allow to build loosely coupled applications
Small components …
- Independent
- Reusable
- Interchangeable
… plugged together to
form a bigger system
Benefits :
- Small classes with single
responsibility
- Easier maintenance
- Extensibility
- Testable
- 10. Example : Boring Bank™ System
• Features
– User can list his
accounts
– User can rename his
accounts
– User can transfer money
from an account to the
other
• Tech :
– Web front-end
– Relational database
- 11. Starting from scratch
public class AccountController : BaseController
{
// GET: Account
[HttpGet]
public ActionResult Index()
{
var userId = this.User.AsClaimsPrincipal().UserId();
using (var context = new BankingDbContext())
{
var accounts = context.Accounts
.Where(a => a.CustomerId == userId)
.OrderBy(a => a.Title).ToList();
return View(accounts);
}
}
[HttpPost]
public ActionResult TransferPost(int from, int to, decimal amount)
{
var userId = this.User.AsClaimsPrincipal().UserId();
using (var context = new BankingDbContext())
{
var accountFrom = context.Accounts
.Single(a => a.CustomerId == userId && a.Id == from);
var accountTo = context.Accounts
.Single(a => a.CustomerId == userId && a.Id == to);
accountFrom.Balance -= amount;
accountTo.Balance += amount;
context.SaveChanges();
return RedirectToAction("Index");
}
}
data
business
presentation
- 12. Tightly-coupled code
• Using another kind of UI ?
• Using another kind of storage ?
• Using the business rules somewhere else ?
- 13. Separation of Concerns
• Layered architecture / split assemblies
– Presentation
– Business
– Data-access (Repository)
• Separated :
– Persistence Entities
– Domain Entities
– View Models
- 14. public class AccountController : BaseController
public Account GetAccountForCustomer(int customerId, int accountId)
{
// GET: Account
[HttpGet]
public ActionResult Index()
{
var userId = this.User.AsClaimsPrincipal().UserId();
public void Transfer(int userId, int fromAccountId, int toAccountId, decimal amountToTransfer
var userAccountService = new UserAccountService();
var accounts = userAccountService.GetAccountsForCustomer(userId);
return View(ToViewModel(accounts));
}
[HttpPost]
public ActionResult TransferPost(int from, int to, decimal amount)
{
var userId = this.User.AsClaimsPrincipal().UserId();
var userAccountService = new UserAccountService();
userAccountService.Transfer(userId, from, to, amount);
return RedirectToAction("Index");
}
AccountController.cs (WebPortal)
UI talks to Business
{
// TODO : validate arguments
var accountRepository = new AccountRepository();
var fromAccount = accountRepository.GetAccountForCustomer(userId, fromAccountId);
var toAccount = accountRepository.GetAccountForCustomer(userId, toAccountId);
// TODO : verify that there is enough money
fromAccount.Balance -= amountToTransfer;
toAccount.Balance += amountToTransfer;
accountRepository.Update(fromAccount);
accountRepository.Update(toAccount);
}
UserAccountService.cs (Business)
Business talks to Data
{
using (var context = new BankingDbContext("BankingDbContext"))
{
var account = context.Accounts
.Single(a => a.CustomerId == customerId && a.Id == accountId);
return account;
}
}
public void Update(Account account)
{
using (var context = new BankingDbContext("BankingDbContext"))
{
var accountEf = context.Accounts.Find(account.Id);
// theoretically, could do "if not changed"
accountEf.Balance = account.Balance;
accountEf.Title = account.Title;
context.SaveChanges();
}
}
AccountRepository.cs (Data)
- 16. anti-pattern : Control Freak
• Symptoms:
– Code insists on how the dependencies are built
– Makes it impossible to use component in isolation
– Not testable without full stack
• Easy to spot : new everywhere
AccountController : BaseController
Account
HttpGet]
ActionResult Index()
userId = this.User.AsClaimsPrincipal().UserId();
userAccountService = new UserAccountService();
accounts = userAccountService.GetAccountsForCustomer(userId);
return View(ToViewModel(accounts));
public void Transfer(int userId, int fromAccountId, int toAccountId
{
// TODO : validate arguments
var accountRepository = new AccountRepository();
var fromAccount = accountRepository.GetAccountForCustomer
var toAccount = accountRepository.GetAccountForCustomer
// TODO : verify that there is enough money
fromAccount.Balance -= amountToTransfer;
toAccount.Balance += amountToTransfer;
accountRepository.Update(fromAccount);
accountRepository.Update(toAccount);
}
- 17. Unit tests as a Coupling Detector
• Unit tests are “just another client” for your
code
• If unit tests are hard to write, the code is
probably too tightly coupled
-> Let’s make it testable !
- 18. Making it testable - Properties
public class UserAccountService
{
[TestMethod]
public void RenameAccount_must_UpdateAccountName()
{
public UserAccountService()
{
AccountRepository = new AccountRepository("BankingContext");
}
#region Dependency Management
public AccountRepository AccountRepository { get; set; }
#endregion
Settable property allows
to “inject” another instance
// Arrange
var newName = "someName";
var existingAccount = AnAccount();
var sut = new UserAccountService();
sut.AccountRepository = FAIL FAIL//I want to put a fake here !
// Act
sut.RenameAccount(existingAccount.CustomerId, existingAccount.Id,
newName);
// Assert
// I want to verify what happened ..
}
In UserAccountServiceTest.cs , in test project Business.Tests
- 19. Programming to an interface
public class UserAccountService : IUserAccountService
[TestMethod]
{
public void RenameAccount_must_UpdateAccountName()
{
public UserAccountService()
{
// Arrange
var newName = "someName";
AccountRepository = new AccountRepository("BankingContext");
}
var existingAccount = AnAccount();
#region Dependency Management
var mockRepo = new Mock<IAccountRepository>();
mockRepo.Setup(r => r.GetAccountForCustomer(It.IsAny<int>(), It.IsAny<int>()))
public IAccountRepository AccountRepository { get; set; }
.Returns(existingAccount);
var sut = new UserAccountService();
sut.AccountRepository = mockRepo.Object; //I want to put a fake here !
#endregion Use an interface (or abstract class) instead of concrete class
// Act
sut.RenameAccount(existingAccount.CustomerId, existingAccount.Id, newName);
// Assert
mockRepo.Verify(r=> r.Update(It.Is<Data.Account>(a=> a.Title == newName)));
}
Inject fake instance
- 20. pattern : Property Injection
Expose settable properties to modify dependencies
Benefits
• Useful to provide optional extensibility
• There must be a good “local default” implementation
Caveats
• Not very easy to discover point of extension
• Easy to forget
• Extra care to avoid NullReferenceExceptions, handle
thread-safety etc
- 21. Making it more explicit - Constructor
public class UserAccountService : IUserAccountService
{
private readonly IAccountRepository _accountRepository;
Injection constructor
used in tests - declare required dependencies as constructor parameters
public UserAccountService(IAccountRepository accountRepository)
{
public IAccountRepository AccountRepository { get { return _accountRepository; if (accountRepository == null) throw new ArgumentNullException("accountRepository
_accountRepository = accountRepository;
}
public UserAccountService()
:this(new AccountRepository("BankingContext"))
{
}
#region Dependency Management
Default constructor
used in production code
[TestMethod]
public void RenameAccount_must_UpdateAccountName()
{
// Arrange
var newName = "someName";
var existingAccount = AnAccount();
var mockRepo = new Mock<IAccountRepository>();
mockRepo.Setup(r => r.GetAccountForCustomer(It.IsAny<int>(), It.IsAny<int>()))
.Returns(existingAccount);
var sut = new UserAccountService(mockRepo.Object);
// Act
sut.RenameAccount(existingAccount.CustomerId, existingAccount.Id, newName);
// Assert
mockRepo.Verify(r=> r.Update(It.Is<Data.Account>(a=> a.Title == newName)));
}
Inject fake instance
- 22. anti-pattern : Bastard Injection
Enable dependencies for testing, but use hard-code
implementation in production code
• Paradox:
– Lots of efforts to reduce coupling
– … but forcing a hard-coded value
• Test-specific code
• Ambiguity
- 23. Cutting the dependency chain
public class AccountController : BaseController
{
private readonly IUserAccountService _userAccountService;
public class UserAccountService : IUserAccountService
Only 1 constructor - dependencies passed as constructor arguments
{
public private AccountController(readonly IAccountRepository IUserAccountService _accountRepository;
userAccountService)
{
if (userAccountService == null) throw new ArgumentNullException("userAccountService
_userAccountService = userAccountService;
}
public UserAccountService(IAccountRepository accountRepository)
{
if (accountRepository == null) throw new ArgumentNullException("accountRepository
_accountRepository = accountRepository;
AccountController (WebPortal)
}
UserAccountService.cs (Business)
- 24. pattern : Constructor Injection
Declare required dependencies as constructor
parameters
• Declarative
• Discoverable (Intellisense, Reflection …)
• Recommended approach in 99.9% of cases
• Easy to implement
Need Guard clause because C# does not support non-nullable
reference types …
- 26. This is great and everything except …
[InvalidOperationException: An error occurred when trying to create a controller of 'BoringBank.WebPortal.Controllers.AccountController'. Make sure that the controller System.Web.Mvc.DefaultControllerActivator.Create(RequestContext requestContext, System.Web.Mvc.DefaultControllerFactory.GetControllerInstance(RequestContext requestContext
System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext
- 27. The chicken and the egg
IAccountRepository repo = new IAccountRepository();
• Ideal world: Programming to interfaces
vs
• Real world : applications do not work with only
interfaces
• Class instances have to be created and assembled
(=composed) at some point
• This happens only in one place in an application
- 28. pattern : Composition Root
Composition of classes into a larger system should
happen only in one place
• Create one object-graph
• As late as possible
• Only part of the code that can reference concrete types
Where ?
• Only applications have a Composition Root
• There is no Composition Root in a class library
• Extension point depends on the kind of app
- 29. ASP.NET MVC Composition Root
public class AppCompositionRoot : DefaultControllerFactory
• IControllerFactory
• Creates a controller instance based on URL
• DefaultControllerFactory uses default
constructor on Controller
• … but it can be changed !
{
protected override IController GetControllerInstance(RequestContext requestContext
Type controllerType)
{
// how to compose an AccountController ?
if (controllerType == typeof(AccountController))
{
var connectionString = ConfigurationManager
.ConnectionStrings["BankingDbContext"].ConnectionString;
var repo = new AccountRepository(connectionString);
var service = new UserAccountService(repo);
return new AccountController(service);
Controller
composition
}
// standard way in MVC to use default strategy
return base.GetControllerInstance(requestContext, controllerType);
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var factory = new AppCompositionRoot();
ControllerBuilder.Current.SetControllerFactory(factory);
In Global.asax
tell MVC to use our composition root
- 30. Pure DI (aka Poor Man’s DI)
Manual wiring of dependencies
• Very explicit (no « magic »)
• Type-safe
• … but repetitive and boring
var connectionString = ConfigurationManager
.ConnectionStrings["BankingDbContext"].ConnectionString;
var repo = new AccountRepository(connectionString);
var service = new UserAccountService(repo);
return new AccountController(service);
- 32. Benefits of full DI-friendly codebase
• Testability
• Maintainability
• Allows parallel work
• … and more !
• Defined in a centralized location
- 34. Extensibility
public class CachedAccountRepository : IAccountRepository
{
private readonly ICache _cache;
private readonly IAccountRepository _decorated;
• Decorator Pattern
public CachedAccountRepository(ICache cache, IAccountRepository decorated)
{
– Very DI-friendly pattern
var nakedRepo = new AccountRepository(connectionString);
if (cache == null) throw new ArgumentNullException("cache");
if (decorated == null) throw new ArgumentNullException("decorated");
_cache = cache;
_decorated = decorated;
// decorate the nakedRepository with caching features
var • Example longCache = : new caching
DotNetCache(TimeSpan.FromHours(1));
var cachedRepo = new CachedAccountRepository(longCache, nakedRepo);
var service }
= new UserAccountService(cachedRepo);
public IReadOnlyList<Account> GetAccountsForCustomer(int userId)
{
var accounts = _cache.GetOrAdd("accounts_" + userId,
() => _decorated.GetAccountsForCustomer(userId));
return accounts;
}
Decorator
delegate to decorated instance
- 36. DI Container – how they work
• Mapping Abstraction-> Concrete Type
– Usually initialized on app start
– Methods like
Register<IAbstraction,ConcreteType>()
• Method Resolve<TRequired>()
• Recursively resolves dependencies reading
constructor parameters
- 37. public class DependencyConfig
Example - Unity
{
public static void Configure(IUnityContainer container)
{
var connectionString = ConfigurationManager.ConnectionStrings["BankingDbContext"
public class MvcApplication : System.Web.HttpApplication
{
public class AppCompositionRoot : DefaultControllerFactory
protected void Application_Start()
{
private readonly IUnityContainer _unityContainer;
var container = new UnityContainer();
DependencyConfig.Configure(container);
var compositionRoot = new AppCompositionRoot(container);
ControllerBuilder.Current.SetControllerFactory(compositionRoot
{
In Global.asax
.ConnectionString;
container.RegisterType<IAccountRepository, AccountRepository>(
new InjectionConstructor(connectionString));
container.RegisterType<IUserAccountService, UserAccountService>();
}
}
public AppCompositionRoot(IUnityContainer unityContainer)
{
In DependencyConfig
if (unityContainer == null) throw new ArgumentNullException("unityContainer
_unityContainer = unityContainer;
}
protected override IController GetControllerInstance(RequestContext requestContext
controllerType)
{
return (IController) _unityContainer.Resolve(controllerType);
}
}
In CompositionRoot
Register / Resolve (/ Release)
- 38. Aspects of DI
• Composition
• Lifetime Management
• Interception
- 39. Interception
• ~ Dynamic Decorators
• Cross-cutting concerns
– Logging
– Auditing
– Profiling …
• AOP-like !
- 40. public class TimingBehavior : IInterceptionBehavior
{
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext
{
var stopwatch = new Stopwatch();
// Before invoking the method on the original target.
Debug.WriteLine("> {0}.{1}", input.MethodBase.DeclaringType, input.MethodBase.Name);
stopwatch.Start();
// Invoke the next behavior in the chain.
var result = getNext()(input, getNext);
stopwatch.Stop();
// After invoking the method on the original target.
if (result.Exception != null)
{
Debug.WriteLine(
Call to decorated instance
"< {0}.{1} failed - after {3} ms",
input.MethodBase.DeclaringType, input.MethodBase.Name, result.Exception.GetType(),
stopwatch.ElapsedMilliseconds);
}
else
{
Debug.WriteLine("< {0}.{1} - after {2} ms",
input.MethodBase.DeclaringType, input.MethodBase.Name,
stopwatch.ElapsedMilliseconds);
}
Before each method call of decorated class
After each method call
public class DependencyConfig
{
public static void Configure(IUnityContainer container)
{
container.AddNewExtension<Interception>();
var connectionString = ConfigurationManager.ConnectionStrings["BankingDbContext"]
.ConnectionString;
container.RegisterType<IAccountRepository, AccountRepository>(
new InjectionConstructor(connectionString),
new Interceptor<InterfaceInterceptor>(),
new InterceptionBehavior<TimingBehavior>());
container.RegisterType<IUserAccountService, UserAccountService>(
new Interceptor<InterfaceInterceptor>(),
new InterceptionBehavior<TimingBehavior>());
}
}
- 42. Things to remember
• DI Patterns …
– Don’t be a Control Freak
– Constructor Injection is your friend
– Compose you object graphs in one place
– DI Containers are powerful but not magical
• … can help you achieve loosely coupled code
– Maintainable
– Testable
- 43. Going further …
• Mark Seemann’s book
and blog posts
– http://blog.ploeh.dk/
• Conversation about DI
in aspnet vNext
– http://forums.asp.net/t/1989008.aspx?Feedback+
on+ASP+NET+vNext+Dependency+Injection
• SOLID principles
- 47. Late-binding
• Dynamically decide which implementation to
protectuedsoeverride IController GetControllerInstance(RequestContext requestContext,
Type controllerType)
{
// how to compose an AccountController ?
if (controllerType == typeof(AccountController))
{
var repo = LoadInstanceFromPluginFolder<IAccountRepository>();
Plugin scenarios – scan assemblies in a folder for implementations
var service = new UserAccountService(repo);
return new AccountController(service);
}
// standard way in MVC to use default strategy
return base.GetControllerInstance(requestContext, controllerType);
- 50. SOLID
Single Responsibility Principle
Open Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principe
Editor's Notes
- Present you self
- Talk about Dependency Injection
Dependency Injection patterns
You may know about it under a form or another, or ay have used tools
Who has ?
You must unlearn ! Need to understand the philosophy and concepts in order to use the tools properly
It’s easy to misuse the tools and miss some benefits
- There is no magic !
- Let’s see …
Inside a controller
Creating a dbcontext (Entity Framework) … imagine if that was ADO .NET
Selecting a few things
Passing it to the view…
Has anybody ever written code like that ?
That’s only a read page … imagine action with side effects…
Simplified, no error handling whatsoever
You may argue that it is loosely coupled … there’s only one class … but what a class !
- Turned spaghetti into lasagna
- Business depends on Data layer … it should be an implementation detail
Presentation depends on Business which depends on Data … which depends on EF … we’ll see that a bit later
But mostly about using components in isolation … this is not testable right now
- Comment :
First encounter with need for loosely coupled code came from unit tests
Who write unit tests here ? Anybody who writes unit tests first ?
First encounter where you code MUST BE loosely coupled : unit tests ! Actually consume components out of the scope of the application
Tightly coupled code is hard to test or not testable at all … if it’s testable it’s not too tightly coupled
- Interface of abstract class ….
I can’t do new I…..
The closest we can get is this ….
Note that even though we introduced interfaces, we still have a hard reference between all projects
- Used in default Visual Studio Template for MVC apps until not so long ago
- Comment about moving the interface into business
Where to put the interface ? Separate project “Abstractions” ? The consumer owns the interface
- If you remember one thing, it’s that !
- Top down
- Can be done with Adapter pattern
- Traditionnal approach would be :
Modify code
Subclass..