How ASP.NET Core Dependency Injection Works
ASP.NET Core includes a native, lightweight Inversion of Control (IoC) container that provides Dependency Injection (DI) out of the box. This article explains how the built-in DI system operates, focusing on service registration, the three fundamental service lifetimes, constructor injection, and how the runtime automatically manages object disposal without requiring third-party libraries.
The Built-in IoC Container
At the core of ASP.NET Core's dependency injection are two primary
components: IServiceCollection and
IServiceProvider.
During application startup—typically in Program.cs—you
register your application's dependencies into the
builder.Services collection, which implements
IServiceCollection. When the application builds, this
collection compiles into an IServiceProvider, which acts as
the runtime container responsible for instantiating types and resolving
their dependencies whenever they are requested.
Service Lifetimes
When registering a dependency, you must specify its lifetime. The built-in container supports three lifetimes:
Transient (
AddTransient)
Services are created each time they are requested from the service container. This lifetime works best for lightweight, stateless services.Scoped (
AddScoped)
Services are created once per client request (connection). In a web application, a scoped service is shared across all components processing the same HTTP request, and it is disposed of when the request finishes.Singleton (
AddSingleton)
Services are created the first time they are requested (or when provided as an instance at startup) and remain alive for the lifetime of the application. Every subsequent request receives the exact same instance.
Registering Services
To register services, map an abstraction (an interface) to a concrete
implementation in Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Register services with their respective lifetimes
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddSingleton<ICacheService, MemoryCacheService>();
var app = builder.Build();Constructor Injection
ASP.NET Core primarily uses constructor injection to supply dependencies to classes, such as controllers, minimal API endpoints, or other services.
When a class requires a dependency, you declare it as a parameter in
its constructor. The runtime inspects the constructor parameters via
reflection, queries the IServiceProvider for matching
types, creates the necessary instances, and passes them in.
public class OrderService
{
private readonly IOrderRepository _repository;
private readonly IEmailSender _emailSender;
public OrderService(IOrderRepository repository, IEmailSender emailSender)
{
_repository = repository;
_emailSender = emailSender;
}
public void CompleteOrder(int orderId)
{
_repository.MarkAsComplete(orderId);
_emailSender.Send("Order completed.");
}
}If OrderService itself is registered in the DI
container, requesting it will cause the container to recursively resolve
both IOrderRepository and IEmailSender
first.
Automatic Disposal and Cleanup
The container automatically manages the lifecycle of the objects it
creates. If a service implements IDisposable or
IAsyncDisposable, the container will call
Dispose or DisposeAsync when the service's
lifetime ends:
- Transient and Scoped instances are disposed of at the end of the HTTP request scope.
- Singleton instances are disposed of when the application shuts down.