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:
- Framework-provided services — provided by the .NET framework:
Service Type Lifetime Microsoft.Extensions.DependencyInjection.IServiceScopeFactory Singleton IHostApplicationLifetime Singleton Microsoft.Extensions.Logging.ILogger Singleton Microsoft.Extensions.Logging.ILoggerFactory Singleton Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton Microsoft.Extensions.Options.IConfigureOptions Transient Microsoft.Extensions.Options.IOptions Singleton System.Diagnostics.DiagnosticListener Singleton System.Diagnostics.DiagnosticSource Singleton - 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>(); | Yes | Yes | No |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) Examples: services.AddSingleton<IMyDep>(sp => new MyDep()); services.AddSingleton<IMyDep>(sp => new MyDep(99)); | Yes | Yes | Yes |
Add{LIFETIME}<{IMPLEMENTATION}>() Example: services.AddSingleton<MyDep>(); | Yes | No | No |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) Examples: services.AddSingleton<IMyDep>(new MyDep()); services.AddSingleton<IMyDep>(new MyDep(99)); | No | Yes | Yes |
AddSingleton(new {IMPLEMENTATION}) Examples: services.AddSingleton(new MyDep()); services.AddSingleton(new MyDep(99)); | No | No | Yes |
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.