There are quite a few good posts out there on Inversion of Control (IoC) and Dependency Injection (DI) in the ASP.NET Core world, but I felt there was still something to be said, hence this post! Mind you, this is going to be a long one! I once wrote another post on the history of dependency resolution in .NET, you may want to have a look at it. Always keep in mind that this is based on the latest bits, and may still change when it gets to the final version.
At bootstrap, ASP.NET Core will either call the ConfigureServices method, or one with a name following the convention Configure<environment>Services, where <environment> comes from the Hosting:Environment environment variable, and you can also set it in Visual Studio:
It is commonly set to “Development”, “Production”, “Staging”, etc. Keep in mind that if the environment-specific method exists (e.g., ConfigureDevelopmentServices), then the generic one (ConfigureServices) is not called.
The Configure*Services method is passed an instance of IServiceCollection, which is where we get to store our service registrations, so that they can be used by ASP.NET Core further along the pipeline. A service registration needs three things:
The lifetime determines how many times the concrete implementation is going to be built. The ASP.NET Core service provider implementation knows about three different lifetimes:
A registration is an instance of the ServiceDescriptor class that is added to the IServiceCollection instance:
There are several extension methods that make this registration even easier.
What if you want to have custom processing when a service is returned? Simple:
Keep in mind that the concrete service must either implement or inherit from the key service. Also, for the Scoped lifetime, if the concrete class implements IDisposable, the service provider will honor it and call Dispose at the end of the HTTP request.
After Configure*Services, ASP.NET Core calls Configure, also in the Startup class. This is where the core initialization occurs, like, adding all middleware, such as the MVC one, that makes the application works the way we expect it. The Configure method can either have no parameters or it can receive one or more parameters with types that match the registered services. For example, we will always have IApplicationBuilder, IHostingEnvironment, ILoggerFactory, so we can add them as parameters together with our own:
It gives us a good chance to initialize them before the actual fun begins. If we want, we can also pass it just an instance of the dependency resolution service, represented by the IServiceProvider interface, which has been around since the early days of .NET. The ASP.NET Core service provider implements this interface, so having it here just means: “get me the service provider implementation so that I can retrieve services from it”:
In the latest version of ASP.NET Core, there is no globally accessible reference to the built-in service provider, something known as the service locator, of bad reputation. If, for any reason you feel you need it, here is the place to store it. By the way, the Microsoft.Extensions.DependencyInjection package has some good strongly typed extension methods for IServiceProvider, make sure you include it.
Each concrete service class itself can be dependency-injected: by default, it should be a concrete class and have a public parameter-less constructor; if, however, we want to inject dependencies into it, which also need to be registered in the service provider, we can have a constructor that takes these dependencies:
Notice the usage of IServiceProvider again. Another option is to have the constructor contain more concrete dependencies:
You can pass any number of parameters, of any type that is registered in the service provider, normally in one of the Configure*Services methods. If the specified service type is not registered, you get an exception at runtime.
An MVC controller can have dependencies injected through its constructor, like I’ve shown for services:
There was another dependency injection possibility with MVC controllers, which consisted in decorating public properties with a [FromServices] attribute: if a registration exists that matched the property’s type, it would be injected into the property just after the controller is constructed:
This is no longer possible, and the functionality was removed in DNX RC2, although it will still work in RC1. Forget about it.
However, you can still use [FromServices] in an action method:
By default, controller classes themselves do not come from the dependency resolution mechanism. You can achieve that, if, in Configure*Services, you call AddControllersAsServices, passing it either a Type or an Assembly collection:
This allows you to do this:
Each time MVC tries to build an HomeController, it will instead get the same SingletonHomeController. Note that I am not saying that you should do this, but I think you get the idea!
Filters in MVC allow you to intercept action method calls, results and exceptions, and do stuff before, after of instead of the real actions. You have global filters and attribute filters: global filters apply always, and attribute filters only apply in the scope where they are declared – a controller’s class or an action method. Filters themselves can have dependencies injected into them.
In order to declare global filters, you use one of the overloads of the AddMvc method, normally in the Configure*Services method, and registering the filter as a service:
MVC will try to resolve the filter type, if necessary, injecting it any dependencies, in the usual way:
The other option for filters is attributes, but attributes require a public parameter-less constructor. The MVC team came up with an alternative for that, in the form of the ServiceFilterAttribute and TypeFilterAttribute attributes. Both receive a Type and the difference is that the first will try to resolve that type from the services registration and the last one will just instantiate it. Let’s see an example:
So, GlobalFilter is retrieved from the services registration, and, if it is a filter, it will behave accordingly to the context where it is located, in this case, it is the whole controller. SomeFilter, on the other hand, is just instantiated and used, no need to have it registered. Don’t forget that types passed to ServiceFilterAttribute and TypeFilterAttribute have to implement one of the filter interfaces in namespace Microsoft.AspNet.Mvc.Filters otherwise an exception is thrown.
Model classes are declared as parameters to action methods in an MVC controller. There’s a binding mechanism that fills their properties automatically, but we can also use dependency injection here.
First, the whole model can come from dependency injection:
Or, at least, some properties of the model:
MVC views can also take injected services, through the @inject directive:
View components, introduced in ASP.NET MVC Core, can also be injected in pretty much the same way as controllers:
Tag helpers, like view components and controllers, also support constructor injection:
OWIN-style middleware are likewise injectable through the constructor:
You need to change the signature of the Configure*Services method so as to return an IServiceProvider:
The key here is to create an instance of the service provider we want to use, maybe leveraging on the one produced from the service collection (BuildServiceProvider method), and return it here. Of course, if it has more features and lifetimes than the default one, you are free to use them all. The only contract it has to comply to is that of IServiceProvider.
While running your MVC application, you can explicitly ask for services registered in the global (the default or your own) service provider. The HttpContext class exposes a RequestServices property; here you will find the custom service provider that you returned in Configure*Services, or ASP.NET Core’s built-in one. Whenever you have a reference to the current HttpContext (controllers, view components, tag helpers, views, middleware, filters), you get it as well. The “old” ApplicationServices was removed in recent commits, so it won’t make it to the final version of ASP.NET Core and you shouldn’t be using it.
The dependency injection mechanism was substantially changed in ASP.NET Core. The only identical feature seems to be constructor injection, and it is understandable, since it’s what most people should be using anyway. Now every moving part of ASP.NET Core is injectable through the same mechanism, which I think is a good thing. The problems so far have been the relative instability in the ASP.NET Core, things have been changing a lot, and there is still no light at the end of the tunnel.