Chapter 8: An introduction to dependency injection
This introduction positions dependency injection as an essential ASP.NET Core pattern for loosely coupled applications and previews how the chapter will introduce and use it.
After this chapter you will be able to
- Dependency injection role. Explain that dependency injection is a design pattern for developing loosely coupled code and that ASP.NET Core uses it extensively in both the framework and application code.
- Manual dependency graph problems. Explain why manually constructing an EmailSender and all of its transitive dependencies in the RegisterUser handler becomes problematic as services gain dependencies. Describe the resulting SRP violation, boilerplate ceremony, tight coupling to implementation details, and difficulty reusing expensive instances.
- DI container construction role. Describe how dependency injection inverts manual object creation: the framework or DI container provides an already-created EmailSender to RegisterUser. Explain that the DI container constructs a requested service by creating its dependencies and passing them to constructors, thereby building the dependency graph.
- Coupling and constructor dependencies. Explain why directly creating an EmailSender inside RegisterUser creates tight coupling, and how receiving the dependency as a parameter reduces that coupling.
- Interface-based dependency. Describe how making RegisterUser depend on IEmailSender rather than the concrete EmailSender further decouples the application, provided each implementation exposes the required SendEmail behavior.
- Service registration mapping. State that service registration tells the DI container which implementation to provide for a requested service, typically by mapping an interface such as IEmailSender to an implementation such as EmailSender.
- Built-in DI container role. Explain that ASP.NET Core includes a simple built-in DI container, used by framework libraries to register themselves and their dependencies. Describe how this supports ASP.NET Core’s modular, composable, plugin-style architecture.
- Handler dependency resolution. Trace how the hosting model fulfills a minimal API handler dependency: when routing selects a handler such as RegisterHandler, RequestDelegateFactory asks the DI container for its IEmailSender dependency, and the container creates the registered implementation such as EmailSender.
- Framework service registration. Explain why nontrivial ASP.NET Core applications must register services for optional framework features such as Razor Pages and authentication, even though many core services are registered automatically.
- Service collection registration point. Describe registering services through the WebApplicationBuilder.Services property in Program.cs, and state that this property is an IServiceCollection containing the services known to the DI container.
- Add extension method convention. Describe using a library's Add*() extension method on WebApplicationBuilder.Services to register all of that library's required components, using AddRazorPages() as the example.
- Endpoint handler service injection. Describe how to inject a registered service such as LinkGenerator into a minimal API endpoint handler by declaring it as a handler parameter. Explain that the minimal API infrastructure asks the DI container for the service and passes the resolved instance to the handler.
- Program service retrieval. Describe how code in Program.cs outside a request obtains registered services through WebApplication.Services, which exposes an IServiceProvider. Distinguish GetService<T>(), which returns null when unavailable, from GetRequiredService<T>(), which throws InvalidOperationException, and state why GetRequiredService<T>() is generally preferred.
What it builds on
- Adding and configuring services (chapter 3) · The earlier introduction to adding and configuring application services.
- Injecting services (chapter 7) · The earlier use of injected services in minimal API handlers.
- Defining route handlers with functions (chapter 5) · Minimal API route handlers and functions that ASP.NET Core invokes for matched requests.
- Endpoint routing in ASP.NET Core (chapter 6) · Endpoint routing selecting an endpoint from an incoming request.
- Generating URLs for a minimal API endpoint with LinkGenerator (chapter 6) · LinkGenerator as a framework service for generating URLs.
- Program.cs file: Defining your application (chapter 3) · Program.cs as the place where an application is defined.
Section by section
Introduction
This introduction positions dependency injection as an essential ASP.NET Core pattern for loosely coupled applications and previews how the chapter will introduce and use it.
- Dependency injection role: Explain that dependency injection is a design pattern for developing loosely coupled code and that ASP.NET Core uses it extensively in both the framework and application code.
Why it matters: DI is necessary for working effectively with all but the most trivial ASP.NET Core applications. - Chapter DI learning path: Describe the chapter's progression: it introduces DI and its principles and benefits, shows ASP.NET Core's use of DI, and teaches how to retrieve services from the DI container.
Why it matters: This roadmap helps you place the later DI sections in a coherent sequence from concept to use. - Subsequent DI topics: State that chapter 9 will show how to apply DI to custom classes, including configuring the app for framework-created classes and controlling object lifetimes, while chapter 31 covers advanced DI options such as third-party containers.
Why it matters: Knowing what is deferred prevents you from expecting service registration and lifetime details in this introductory chapter.
Key terms: dependency injection, DI, loosely coupled code, ASP.NET Core, DI container, services, object lifetimes, third-party DI container
8.1 Understanding the benefits of dependency injection
This section introduces dependency injection as a way for ASP.NET Core to construct object dependency graphs so application code can focus on using services instead of creating them.
- Manual dependency graph problems: Explain why manually constructing an EmailSender and all of its transitive dependencies in the RegisterUser handler becomes problematic as services gain dependencies. Describe the resulting SRP violation, boilerplate ceremony, tight coupling to implementation details, and difficulty reusing expensive instances.
Why it matters: Recognizing these costs shows why DI becomes valuable as an application grows beyond simple objects with no dependencies. - DI container construction role: Describe how dependency injection inverts manual object creation: the framework or DI container provides an already-created EmailSender to RegisterUser. Explain that the DI container constructs a requested service by creating its dependencies and passing them to constructors, thereby building the dependency graph.
Why it matters: The container centralizes object construction, leaving handlers and services focused on their actual application work. - Explicit dependency declaration: Distinguish implicit dependencies, which a class creates internally and which are visible only by inspecting its implementation, from explicit dependencies supplied through parameters or constructors. State that DI encourages explicit dependencies, which are easier to reason about and test.
Why it matters: Making required collaborators visible makes a component's requirements clearer and reduces hidden coupling. - DI and IoC relationship: Distinguish inversion of control from dependency injection in the ASP.NET Core context. Explain that IoC lets the framework control when it invokes registered handlers, while DI goes further by having the framework also create and provide the handler's dependencies; DI is a specific form of the broader IoC principle.
Why it matters: This distinction helps you describe what the framework controls and what additional problem DI solves.
Key terms: dependency graph, single-responsibility principle, SRP, ceremony, implicit dependency, explicit dependency, dependency injection, DI container, IoC container, service
8.2 Creating loosely coupled code
This section shows how dependency injection promotes loosely coupled, testable code by passing dependencies as interfaces and registering their implementations in a DI container.
- Coupling and constructor dependencies: Explain why directly creating an EmailSender inside RegisterUser creates tight coupling, and how receiving the dependency as a parameter reduces that coupling.
Why it matters: Reducing coupling means a class has fewer details to change when one of its dependencies changes. - Interface-based dependency: Describe how making RegisterUser depend on IEmailSender rather than the concrete EmailSender further decouples the application, provided each implementation exposes the required SendEmail behavior.
Why it matters: Depending on an interface lets production and test code use suitable implementations without changing the consumer. - Test implementation substitution: Explain how an IEmailSender dependency allows unit tests to substitute a DummyEmailSender, stub, or mock so tests of RegisterUser do not send real emails.
Why it matters: Substituting a safe test implementation prevents unit tests from triggering real external effects such as sending email. - Service registration mapping: State that service registration tells the DI container which implementation to provide for a requested service, typically by mapping an interface such as IEmailSender to an implementation such as EmailSender.
Why it matters: Registration lets the application choose the production implementation while consumers remain independent of that choice.
Key terms: coupling, tight coupling, loosely coupled code, RegisterUser, EmailSender, dependency, abstraction, interface, IEmailSender, SendEmail
8.3 Using dependency injection in ASP.NET Core
This section explains how ASP.NET Core’s built-in DI container supplies registered dependencies to minimal APIs and other framework components.
- Built-in DI container role: Explain that ASP.NET Core includes a simple built-in DI container, used by framework libraries to register themselves and their dependencies. Describe how this supports ASP.NET Core’s modular, composable, plugin-style architecture.
Why it matters: The container is the central mechanism through which ASP.NET Core components obtain the services they need. - Handler dependency resolution: Trace how the hosting model fulfills a minimal API handler dependency: when routing selects a handler such as RegisterHandler, RequestDelegateFactory asks the DI container for its IEmailSender dependency, and the container creates the registered implementation such as EmailSender.
Why it matters: This explains how a service declared as a handler dependency becomes available when ASP.NET Core handles a request. - Framework service extensibility: Describe how registering an implementation makes it injectable into application services and framework handlers, including framework services such as LinkGenerator. Explain that alternative implementations of framework services can be registered so the framework uses them instead of its defaults, and that Razor Pages and MVC use DI similarly to create handler dependencies.
Why it matters: DI lets an application choose and replace components while allowing the framework to connect those components automatically.
Key terms: DI container, built-in container, services, modular, composable, hosting model, RequestDelegateFactory, RegisterHandler, IEmailSender, EmailSender
8.4 Adding ASP.NET Core framework services to the container
This section explains how ASP.NET Core features and libraries register their required framework services with the built-in DI container.
- Framework service registration: Explain why nontrivial ASP.NET Core applications must register services for optional framework features such as Razor Pages and authentication, even though many core services are registered automatically.
Why it matters: Features cannot run unless the DI container has the services they require. - Service collection registration point: Describe registering services through the WebApplicationBuilder.Services property in Program.cs, and state that this property is an IServiceCollection containing the services known to the DI container.
Why it matters: Knowing the registration point lets you enable framework features during application startup. - Add extension method convention: Describe using a library's Add*() extension method on WebApplicationBuilder.Services to register all of that library's required components, using AddRazorPages() as the example.
Why it matters: Add* methods enable features without requiring you to manually wire up every internal service. - Missing service diagnosis: Explain that a library's documentation determines whether it requires service registration, and describe missing registration as causing nonworking functionality or a startup exception that should prompt registration of the required services.
Why it matters: Recognizing missing-service failures helps you correct startup configuration quickly.
Key terms: DI container, framework services, core services, Razor Pages, authentication, WebApplicationBuilder, Services, IServiceCollection, Program.cs, Add*() extension method
8.5 Using services from the DI container
This section explains how minimal API applications obtain registered services through endpoint-handler injection or, when startup code needs them, directly from WebApplication.Services.
- Endpoint handler service injection: Describe how to inject a registered service such as LinkGenerator into a minimal API endpoint handler by declaring it as a handler parameter. Explain that the minimal API infrastructure asks the DI container for the service and passes the resolved instance to the handler.
Why it matters: Handler injection is the normal, recommended way for endpoint code to receive the services it needs. - Dependency graph resolution: Explain that the DI container resolves an entire dependency graph: when it creates a service such as LinkGenerator, it first resolves the constructor dependencies required by that service. State that whether a service is newly created or reused depends on its registration lifetime.
Why it matters: This explains why declaring one dependency can cause DI to assemble all of the supporting objects it needs. - Program service retrieval: Describe how code in Program.cs outside a request obtains registered services through WebApplication.Services, which exposes an IServiceProvider. Distinguish GetService<T>(), which returns null when unavailable, from GetRequiredService<T>(), which throws InvalidOperationException, and state why GetRequiredService<T>() is generally preferred.
Why it matters: Startup tasks such as database schema updates or logging may need a service before the application starts handling requests. - Service locator boundaries: Explain that directly requesting a class from IServiceProvider is the service locator pattern and should generally be avoided in application types and endpoint handlers. State the exception that Program.cs needs this approach to access DI services in the application's main loop outside a request.
Why it matters: Keeping dependencies explicit as constructor or handler arguments makes code follow the intended DI style.
Key terms: endpoint handler, LinkGenerator, DI container, minimal API infrastructure, dependency graph, constructor, dependencies, lifetime, Program.cs, WebApplication.Services
8.6 Summary
This summary recaps how ASP.NET Core’s dependency-injection container registers, creates, and supplies services, and why explicit dependencies are preferred.
Where this is used later
- Registering custom services with the DI container (chapter 9) · This section applies DI by registering custom services with the container.
- Understanding lifetimes: When are services created? (chapter 9) · This section explains the object lifetimes previewed in the introduction.
- Using a third-party dependency injection container (chapter 31) · This section covers using a third-party dependency injection container.
- Replacing dependencies in WebApplicationFactory (chapter 36) · Replacing dependencies in WebApplicationFactory depends on dependencies being provided externally.
- Registering a service in the container multiple times (chapter 9) · The benefit of coding against abstractions to keep code loosely coupled.
- Injecting multiple implementations of an interface (chapter 9) · Multiple implementations can be injected for a shared interface.
- Using typed clients to encapsulate HTTP calls (chapter 33) · Typed clients are dependencies whose implementations are supplied through DI.
- Adding Fact and Theory unit tests (chapter 35) · Unit tests can use substitute dependencies when testing code behavior.
Concepts explained in this chapter
- dependency injection containerThe built-in dependency injection container is the default service container provided by ASP.NET Core for registering and resolving application services.
- dependency graphA dependency graph is a directed graph representing objects or components and the dependencies required to construct or operate them.
- couplingLoose coupling is a software design property in which components have limited knowledge of and dependence on one another.
- dependency injectionDependency injection is a design pattern in which an object receives its dependencies from an external provider rather than creating them itself.
- direct dependencyAn explicit dependency is a dependency declared by a component's public construction or method interface and supplied from outside the component.
- implicit dependencyAn implicit dependency is a required object or service obtained indirectly rather than declared through a component's parameters or constructor arguments.
- SOLID principlesSOLID is a mnemonic for five object-oriented design principles: single responsibility, open-closed, Liskov substitution, interface segregation, and dependency inversion.
- constructor injectionConstructor injection is a dependency injection technique in which required dependencies are supplied through an object's constructor.
- dependency inversionThe dependency inversion principle is a design principle in which high-level modules depend on abstractions rather than low-level implementation details.
- service registrationService registration is the configuration that maps a requested service or interface to the implementation the dependency-injection container should provide.
- service locator patternThe service locator pattern is a design pattern in which application code directly requests dependencies from a central service provider.
- serviceA service is a class that provides a discrete piece of functionality to an application and can be registered with its dependency-injection container.
- handlerA handler is an application component that receives a framework-dispatched request or event and performs the corresponding processing.
- services propertyThe Services property is the WebApplicationBuilder property through which application and framework services are registered for dependency injection.
- IServiceCollectionIServiceCollection is the ASP.NET Core abstraction representing the collection of service registrations available to an application.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
