Dependency Injection [ Documentation]
See notes on Dependency Injection in .NET.
Makes configured services available throughout an app. ASP.NET Core templates automatically register over 250 services including:
IApplicationBuilderFactory
IServiceProvider
as a built-in service containerILogger<T>
(so a logger services does not need to be registered separately)ILoggerFactory
IHostApplicationLifetime
IHostLifetime
IHostEnvironment
orIWebHostEnvironment
IServer
IOptions<T>
DiagnosticSource
DiagnosticListener
Services are added to the DI container with WebApplicationBuilder.Services
in Program.cs
.
When WebApplicationBuilder
is instantiated, many framework-provided services are added.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the DI (services) container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
WebApplication app = builder.Build();
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.
Registering Groups of Services
In ASP.NET, this is done with an AddGroup_Name
extension method:
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
Service Lifetimes
ASP.NET Core uses the standard .NET service lifetimes.
To inject scoped services, either:
- Inject the service into middleware’s Invoke or InvokeAsync method.
Do not use constructor injection (this throws a runtime exception because it forces scoped services to behave like a singleton).
- Use Factory-based middleware.
Service Registration Methods
ASP.NET Core uses standard .NET service registration methods.
Notes:
- If a service is registered with only an implementation type, you have registered that service with the same implementation and service type.
- Stated differently: multiple implementations of a service cannot be registered using methods that don’t take an explicit service type.
- Stated differently: multiple instances of the service can be registered, but they will all have the same implementation type.
To exemplify:
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>(); // Overrides the previous call when resolved as IMyDependency; add to the previous call when resolved as IEnumerable<MyDependency>
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Entity Framework Contexts
By default, Entity Framework contexts are added to the service container using the scoped lifetime because web app database operations are normally scoped to the client request. To use a different lifetime, specify the lifetime by using an AddDbContext
overload. Services of a given lifetime shouldn’t use a database context with a lifetime that’s shorter than the service’s lifetime.
Requesting Services
Use HttpContext.RequestServices
which will expose the scoped service provider.
Disposal of Services
Dispose
for the IDisposable
services it creates.
Here, Service1
, Service2
, and Service3
are created by the DI container:
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));
Here, they are not:
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
In the second example, Service1
and Service2
must be disposed of manually.