Skip to content

09 - Dependency Injection

Dependency Inversion Principle

  • Theory was first formalized by Robert C. Martin, 1996
  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend upon details. Details should depend upon abstractions
  • Example from life – myriad of chargers and small-scale electronic devices

Hard dependencies

  • Every module is with its own interface
  • Higher module must support them all separately
  • Modules are not reusable

DI

Dependency Inversion

  • Higher module specifies the required interface
  • All modules depend on the same interface
  • Modules are reusable

DI

Inversion of Control

Software development pattern
Implements the Dependency Inversion principle

  • Control over how different systems interface with each other
  • Control over program events (command line vs graphical UI)
  • Control over dependency creation and binding

Dependency Injection

  • Implementation of IoC (DIP -> IoC -> DI)
  • Dependency creation and resolving of them is moved outside of dependent class

DI

Caution

  • Internal principles of class behavior will be exposed
  • Dependencies are created before they are needed
  • Don’t overuse – is it needed for everything?

Meta example

Using constructor

1
2
3
4
5
6
7
8
9
IRepo xmlrepo = new XMLRepo();
Controller Cnt = new Controller(xmlrepo);

public class Controller{
    private readonly IRepo _repo;
    public Controller(IRepo repo){
        _repo = repo;
    }
}

Meta example MVC

  • During testing you can supply different implementation of repository
  • In regular use default constructor is used and dependency is created
1
2
3
4
5
6
7
public class PersonController:Controller{
    private readonly IRepo _repo;
    public PersonController (IRepo? repo = null){
        _repo = repo ?? new PersonRepository();
    }
    public PersonController() : this.PersonController(null) { }
}

Dependency Injection Container

  • Framework
  • Registers interfaces and their implementations
  • Resolves dependencies and provides (injects) correct implementation
  • Lifecycle management of created objects
  • Several implementations - widely used are
    • ASP.NET Core – has built-in lightweight solution out-of-the-box
    • Ninject – Open Source
    • Unity - MS official solution
    • Autofac

ASP.NET Core DI - Lifetime

  • Transient
    • Transient lifetime services are created each time they're requested. This lifetime works best for lightweight, stateless services.
  • Scoped
    • Scoped lifetime services are created once per http request.
  • Singleton
    • Singleton lifetime services are created the first time they're requested. Every subsequent request uses the same instance. If the app requires singleton behavior, allowing the service container to manage the service's lifetime is recommended.
    • Don't implement the singleton design pattern and provide user code to manage the object's lifetime in the class.

ASP.NET Core DI - EF

  • Entity Framework contexts should be added to the service container using the scoped lifetime.
  • This is handled automatically with a call to the AddDbContext<> method when registering the database context.
  • Services that use the database context should also use the scoped lifetime.

ASP.NET Core DI - Constructor injection

Services can be resolved by two mechanisms:

  • IServiceProvider
  • ActivatorUtilities – Permits object creation without service registration in the dependency injection container. ActivatorUtilities is used with user-facing abstractions, such as Tag Helpers, MVC controllers, and model binders.


  • Constructors can accept arguments that aren't provided by dependency injection, but the arguments must assign default values.
  • When services are resolved by IServiceProvider or ActivatorUtilities, constructor injection requires a public constructor.
  • When services are resolved by ActivatorUtilities, constructor injection requires that only one applicable constructor exists. Constructor overloads are supported, but only one overload can exist whose arguments can all be fulfilled by dependency injection.

Automatic registration to DI

  • https://github.com/khellang/Scrutor
  • https://github.com/JonPSmith/NetCore.AutoRegisterDi
    • https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/