ASP.NET 5 Dependency Injection – Lifetimes

Dependency injection is a fundamental part of ASP.NET 5, and before we continue our discussion of configuration it will be helpful to see how we work with dependency injection in ASP.NET 5.

This post assumes you understand Inversion of Control (IoC) and Dependency Injection (DI). In a nutshell, these principles allow us to keep our classes loosely coupled. Instead of explicitly creating instances that our class needs (its dependencies, which ASP.NET 5 refers to as services), we have them passed in as constructor arguments (constructor injection) or set via properties (setter injection). At run-time we can use different types to fulfill these dependencies, which is useful in plugin scenarios and makes our class more testable since we can easily pass in mocks of its dependencies.

To ease the burden of working with classes designed to use DI (imagine having to manually create the dependencies of a class with a large dependency graph), we use an IoC container. We register service implementations with the container, and when we need a class instance, the container takes care of constructing the dependency graph for us.  The container can also manage service lifetimes, returning a new instance for each request, or the same one for all requests.

ASP.NET 5 defines the following lifetimes:

Singleton Create and use a single instance over the lifetime of the application.
Scoped Create a new instance for each scope (ASP.NET creates a scope around each HTTP request).
Transient The container creates a new instance each time we ask for a service.

To register service implementations we use the IServiceCollection interface, which has extension methods to register implementations for each of the service lifetimes. These methods allow you to make a direct association between a service type and implementation type, or to associate a service type with a factory method to create new instances.

Note: In RC1, there are two IServiceCollection extension methods named AddInstance; these have since been renamed to become overloads of AddSingleton.

To get an IServiceCollection we define a method named ConfigureServices in our Startup class. This method has an IServiceCollection parameter, which we can then use to register our service implementations.

IServiceCollection has extension methods for the following registration scenarios:

Except for the last scenario (adding a singleton instance), there are corresponding extension methods for the Scope and Singleton lifetimes, and all scenarios have equivalent non-generic extension methods.

As mentioned earlier, after RC1 the AddInstance methods became overloads of AddSingleton.

IServiceProvider is the interface to the DI container, but we usually don’t work with it directly. Instead, the classes we write have their dependencies injected via constructor arguments or properties and code somewhere upstream works with IServiceProvider to get an instance of our class (or another class that has ours in its dependency graph). In cases where you need to request a service directly from IServiceProvider, use the HttpContext.RequestServices property to get an IServiceProvider scoped to the HTTP request:

Besides the GetService<T> extension method, IServiceProvider also has a GetRequiredService<T> extension method, which throws an InvalidOperationException if there isn’t a service type T registered (GetService<T> returns null in this case), and a GetServices<T> extension method that returns an IEnumerable<T>. There are also non-generic equivalents of these extension methods which take a Type.

ASP.NET 5 comes with a default IoC container implementation that supports only constructor injection. In this example, we use the default container to explore service lifetimes.

We start with a basic project.json file:

We then define interfaces so we can register the same class for each of the lifetimes. The base interface IService will include properties that we can use to check instance identity, as well as a property of type IOtherService, which we will use to test dependency injection:

The MyService class implements each of the service lifetime interfaces, and expects an IOtherService constructor argument:

The IOtherService interface contains only a Guid property for identification, which the MyOtherService class generates:

In the Startup class, we put everything together. We define a ConfigureServices method (line 9) and use the services parameter to register our services. Note that we register MyOtherService for IOtherService using the Scoped lifetime. When our application receives a request, we use the RequestServices property of the HttpContext, which is an IServiceProvider, to get instances for each of the registered services (lines 21-28). We can then write out various properties of the instances to see how lifetimes behave.

Running the application and browsing to the site returns the following response:

di-1-request-1

As expected, the singleton1 and singleton2 variables refer to the same instance, as do the scoped1 and scoped2 variables, while the transient1 and transient2 variables refer to different instances. Note that the IoC container took care of creating the dependencies needed by MyService, and that the same instance of MyOtherService was provided for the scoped and transient instances of MyService, but the singleton received a different instance since it is outside the scope of the request.

Refreshing the page, we see the singleton remains the same, but the scoped instance has changed:

di-1-request-2

You can download source code for the example projects here:

In this example we used the default IoC container provided with ASP.NET, but you can also substitute in an IoC container of your choice. In the next post we’ll explore using two popular containers: Autofac and StructureMap.

2 Comment

  1. […] ASP.NET 5 Dependency Injection – Lifetimes […]

  2. Buddhima says: Reply

    Thanks ! This was really helpful for me..

Leave a Reply