Abstract

https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines https://scottlilly.com/c-design-patterns-the-dependency-injection-pattern/ https://auth0.com/blog/dependency-injection-in-dotnet-core/

Dependency Injection is a design pattern used to achieve Inversion of Control between classes and their dependencies. A dependency is an object that another object depends on.

IoC Container

  • Implements IServiceProvider
  • Dependencies managed by this container are called services.
  • Microsoft.Extensions.DependencyInjection.IServiceCollection.

Functions of the IoC Container

  • Registration — mapping a type to a class so that the IoC can create the correct dependency instance.
  • Resolution — resolving dependencies by creating an object and injecting it into the requesting class.
  • Disposition — managing the dependency’s lifetime.

Two Types of Services

There are two types of services:

  1. Framework-provided services — provided by the .NET framework:
    Service TypeLifetime
    Microsoft.Extensions.DependencyInjection.IServiceScopeFactorySingleton
    IHostApplicationLifetimeSingleton
    Microsoft.Extensions.Logging.ILoggerSingleton
    Microsoft.Extensions.Logging.ILoggerFactorySingleton
    Microsoft.Extensions.ObjectPool.ObjectPoolProviderSingleton
    Microsoft.Extensions.Options.IConfigureOptionsTransient
    Microsoft.Extensions.Options.IOptionsSingleton
    System.Diagnostics.DiagnosticListenerSingleton
    System.Diagnostics.DiagnosticSourceSingleton
  2. Application services — services created by the developer; must be registered explicitly.

Adding dependencies

Use the Add method to register generic dependencies (application services):

public void ConfigureServices(IServiceCollection services) {
    services.Add(new ServiceDescriptor(typeof(ILog), new MyLogger()));
}

Service Lifetimes

Services can be registered with one of these lifetimes: Transient, Scoped, Singleton

Transient

  • DI container returns new instance of the service each time it is requested.
  • Register services with AddTransient.
  • In apps that process requests, transient services are disposed of at the end of the request.

Scoped

  • In a web app, DI container returns same instance of service for multiple requests within the scope of a single HttpRequest (returns different instances across multiple HttpRequests).
  • Register services with AddScoped.
  • In apps that process requests, scoped services are disposed of at the end of the request.
  • In EF Core, AddDbContext registers DbContext types with a scoped lifetime.

Singleton

  • DI container returns the same instance of the service for all requests.
  • All subsequent requests of the service from the DI container use the same instance.
  • Register services with AddSingleton.
  • The DI container disposes of Singleton lifetime services automatically. Services should never be disposed by code that resolved the service from the container.
  • In apps that process requests, singleton services are disposed when the app is shutdown. Consider memory use.

TryAdd

Use TryAddLifetime to register the service only if there isn’t already an implementation registered.

Service Registration Methods

Services can be registered the following ways:

Method

Automatic

object

disposal

Multiple

implementations

Pass args

Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()

Example:

services.AddSingleton<IMyDep, MyDep>();

YesYesNo

Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})

Examples:

services.AddSingleton<IMyDep>(sp => new MyDep());

services.AddSingleton<IMyDep>(sp => new MyDep(99));

YesYesYes

Add{LIFETIME}<{IMPLEMENTATION}>()

Example:

services.AddSingleton<MyDep>();

YesNoNo

AddSingleton<{SERVICE}>(new {IMPLEMENTATION})

Examples:

services.AddSingleton<IMyDep>(new MyDep());

services.AddSingleton<IMyDep>(new MyDep(99));

NoYesYes

AddSingleton(new {IMPLEMENTATION})

Examples:

services.AddSingleton(new MyDep());

services.AddSingleton(new MyDep(99));

NoNoYes

Dependency Injection Chaining

This is common. Each requested dependency in turn requests its own dependencies. The DI container resolves the dependencies in a graph and returns the fully resolved service. This is an object graph.

Constructor Injection

Constructors can accept arguments that are not provided by DI, but the arguments must assign default values. Services resolved by IServiceProvider or ActivatorUtilities require a public constructor.

Multiple Constructor Discovery

When a type has more than one constructor, the service provider determines which constructor to use. The constructor with the most parameters where the types are DI-resolvable is selected.

For example, if logging has been added and is resolvable from the service provider, but FooService and BarService are not, the highlighted constructor is chosen:

public class ExampleService 
{
    public ExampleService() { }

    public `ExampleService(ILogger<ExampleService> logger)` 
    { // omitted for brevity
    }

    public ExampleService(FooService fooService, BarService barService) 
    { // omitted for brevity
    }
}

If there is ambiguity when discovering constructors, an exception is thrown.

Disposal of Services

The DI container disposes services itself. Services resolved from the container must never be disposed by the developer.