liveBook · Manning

Dependency injection

As explained in ASP.NET Core in Action, 3e

Dependency injection is a design pattern in which an object receives its dependencies from an external provider rather than creating them itself.

Dependency injection (DI) lets a class or endpoint declare the services it needs instead of creating them with `new`. An external container supplies those dependencies. This keeps code focused on its own job and makes it easier to replace an implementation without rewriting its consumer. In ASP.NET Core, DI supports both framework features and application services. section 3.7.1 section 8.3

A service is a class or interface registered with the container. Registration tells the container which implementation to provide—for example, to use `EmailSender` when code requests `IEmailSender`. The container also creates the registered dependencies needed to construct that implementation; together, these objects form a dependency graph. section 8.6 section 9.1

A typical setup has three parts:

  • Register services through `builder.Services` in `Program.cs`, before calling `builder.Build()`. Optional framework features often provide an `Add*` method, such as `AddRazorPages()`, that registers their required services together. section 8.4
  • Declare a dependency where it is used. A minimal API handler can take a registered service as a parameter; a Razor Page model can receive one through its constructor. The framework obtains the service and passes it in. section 8.5 section 13.2
  • Choose a lifetime when registering an application service: transient creates an instance for each request for that service, scoped reuses one within a scope (one per web request in ASP.NET Core), and singleton reuses one across scopes. section 9.4

Replace manual construction in a registration endpoint

A handler that creates `EmailSender`, `MessageFactory`, `NetworkClient`, and email-server settings itself must know how the whole chain is built. Instead, give the handler an `IEmailSender` parameter and call `emailSender.SendEmail(username)`. Register `IEmailSender` as `EmailSender` and register its dependencies so the container can construct the chain. If a required service is missing, the application can fail at runtime; an unregistered minimal API parameter may instead be treated as request-body input. section 9.1

Listing 9.3 Registering services with the DI container
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
 
builder.Services.AddScoped<IEmailSender, EmailSender>();   #1
builder.Services.AddScoped<NetworkClient>();               #2
builder.Services.AddSingleton<MessageFactory>();           #3
 
WebApplication app = builder.Build();
 
app.MapGet("/register/{username}", RegisterUser);
 
app.Run();
 
string RegisterUser(string username, IEmailSender emailSender)
{
    emailSender.SendEmail(username);
    return $"Email sent to {username}!";
}
#1 Whenever you require an IEmailSender, use EmailSender.
#2 Whenever you require a NetworkClient, use NetworkClient.
#3 Whenever you require a MessageFactory, use MessageFactory.

The registration example pairs an interface with its implementation, registers two supporting classes, and lets the endpoint request `IEmailSender` directly. The email-server settings used by the full example also need registration. section 9.1

Prefer declared dependencies to fetching services inside handlers. Direct retrieval through `app.Services.GetRequiredService<T>()` is available for work in `Program.cs` outside a request, but using the container as a service locator inside handlers is generally discouraged. Take care with lifetimes, too: a long-lived service capturing a shorter-lived dependency can cause difficult-to-debug behavior. section 8.6 section 9.4

Some integration points need an extra step. C# attributes cannot accept ordinary service dependencies in their constructors, so a filter can put its behavior in a separate class and use `TypeFilterAttribute` or `ServiceFilterAttribute` to obtain it through DI. section 22.3

How ASP.NET Core in Action, 3e covers it

How deep it goes
Works through service registration, dependency graphs, retrieval, lifetimes, and a filter-attribute limitation.
Examples
Contrasts manual and injected email-sender construction, registers Razor Pages services, and compares lifetime behavior using a data context.
What it assumes
Assumes familiarity with C# or a similar object-oriented language; introduces dependency injection from its basic purpose.
What you can do after
Apply it via ASP.NET Core’s built-in container to register services, inject them into handlers, and select lifetimes.
How it connects
Connects to inversion of control, dependency graphs, minimal APIs, Razor Pages, service lifetimes, and the service locator pattern.

Also explained in

Compare how 21 books explain dependency injection →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of ASP.NET Core in Action, 3e. The section links point to the parts of the book it draws on.