The built-in dependency injection (DI) container creates services for ASP.NET Core and supplies them where they are needed. A service can declare other services as constructor parameters; the container builds that dependency graph instead of making an endpoint handler construct it by hand. This keeps dependencies explicit and lets a handler focus on its job. section 8.1 section 8.3
Register services in Program.cs through builder.Services, an IServiceCollection, before calling builder.Build(). WebApplicationBuilder registers many core services automatically. Optional features need their own registration: builder.Services.AddRazorPages(), for example, adds the services required by Razor Pages. Registering a feature and mapping its endpoints are separate steps. section 8.4
Replace manual construction with an injected service
Suppose a registration endpoint sends an email. Rather than constructing EmailSender and all its dependencies inside the handler, give the handler an IEmailSender parameter. Register IEmailSender with EmailSender as its implementation, and register the services EmailSender itself requires. The container can then provide the handler’s argument. Do not overlook a dependency such as EmailServerSettings: the illustrated registrations for EmailSender, NetworkClient, and MessageFactory are not the complete graph. section 8.1 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 listing pairs an interface with its implementation and registers two supporting services using builder.Services. EmailServerSettings still needs registration before the full example can run. section 9.1
In minimal APIs, add a registered service as an endpoint-handler parameter; the framework requests it from the container and passes it to the handler. That is the usual approach. Outside a request, Program.cs can retrieve a service through app.Services.GetRequiredService<T>(). Avoid using this direct lookup inside handlers: it hides their dependencies. GetRequiredService<T>() throws if the service is absent; GetService<T>() returns null. section 8.5 section 8.6
Choose a lifetime when registering each custom service:
- Transient creates a new instance each time the service is requested. section 9.4
- Scoped reuses an instance within one scope; an ASP.NET Core web request has its own scope. section 9.4
- Singleton returns the same instance regardless of scope. Watch for a long-lived service capturing a shorter-lived dependency. section 9.4
You can register several implementations of one interface and request IEnumerable<T> to receive them in registration order. Use IEnumerable<T>, not T[] or ICollection<T>, as the injection parameter. A missing registration can instead cause a runtime exception; for an unknown minimal API handler parameter, the framework may try to bind it from the request body. section 9.3.1 section 9.1
The built-in container is intentionally simple and suits many small applications. If you need features such as assembly scanning, automatic registration, or property injection, a third-party container such as Lamar can replace it. Registering an EF Core DbContext is another practical use of the container: it makes database access available to an application service without putting that work in the endpoint. section 31.3 section 12.2
