Dependency Injection Conventions with ASP.NET Core 1.1 and Autofac
With the 1.1 release of ASP.NET Core, it has now become a little easier to configure 3rd party dependency injection containers into your application. Autofac.Extensions.DependencyInjection
4.1.0 was recently released with support for this new feature, so I figured I'd try it out.
DI Conventions
Ever since ASP.NET MVC was introduced, we've been accustomed to relying on conventions that make the development experience with the framework a little nicer. In ASP.NET Core, probably one of the best examples of this is the Startup
class. This is a simple .NET class that we use to compose the various features of our application. A fairly barebones app only requires that you to create a Configure
method that accepts an instance of IApplicationBuilder
. Then by convention, the framework will discover this method and configure your application. For richer applications that require additional services and wish to leverage the built in dependency injection features, a ConfigureServices
can be added to the Startup
class which will accept an instance of IServiceCollection
that you can use to wire up your services.
public void ConfigureServices(IServiceCollection services)
{
services. AddTransient<IDataService, MyDataService>();
}
Some of us might have existing investments in a particular DI framework that we'd like to integrate into the ASP.NET Core DI infrastructure. To do that, we'd need an implementation of IServiceProvider
that would adapt your framework of choice. For example, if you use Autofac
then the Configure
method might look something like this.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Create the container builder.
var builder = new ContainerBuilder();
builder.RegisterType<MyDataService>().As<IDataService>();
builder.Populate(services);
var container = builder.Build();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(container);
}
Just like the Configure
method, the framework will discover the implementation of ConfigureServices
in the Startup
class and run it for you.
Introducing ConfigureContainer
Starting with ASP.NET Core 1.1, 3rd party DI frameworks are able plug in earlier in the pipeline which has the added benefit of reducing the amount of code needed to use them. At the core of this is the IServiceProviderFactory interface. Frameworks like Autofac
can implement this interface (see here). Then it can be registered when WebHostBuilder
is getting setup. ASP.NET Core can now use this factory to replace its default service provider that gets created at startup. Let's see how we can update the example above to make use of this new feature.
First, make sure you have at least version 4.1.0 of Autofac.Extensions.DependencyInjection
installed in your application. Next, we'll remove that Autofac
setup code from ConfigureServices
and add a new method to the Startup
class, ConfigureContainer
.
public void ConfigureContainer(ContainerBuilder builder) {
builder.RegisterType<FakeDataService>().As<IDataService>();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
Just like Configure
and ConfigureServices
, ConfigureContainer
will get discovered and executed by convention when your application starts. You can also create variations based on the the hosting environment. So for example, you can have ConfigureDevelopmentContainer, ConfigureStagingContainer and so on.
The parameter to the ConfigureContainer
method will be the type that you use to compose your DI container. In the case of Autofac
, this happens with the ContainerBuilder
class. Now any of the registration code for your 3rd party DI framework can now be placed inside of the ConfigureContainer
method.
The last thing that needs to happen is for us to register the IServiceProviderFactory
implementation. Autofac
provides the AddAutofac extension method that helps out with that. Using the ConfigureServices
method off of IWebHostBuilder
, we can update the hosting bootstrap code to plug in Autofac
.
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.ConfigureServices(services => services.AddAutofac())
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
Now a new IServiceProviderFactory
is configured in the application and we can continue registering additional services with Autofac
.
One mistake I made when I was first trying this out was I tried to call the AddAutofac
extension method inside the ConfigureServices
of the Startup
class. This doesn't work because it's too late in the initialization of your application to register a custom IServiceProviderFactory
. It should be called before any of the Startup
methods get called. In this instance, we'll have to use the ConfigureServices
method of IWebHostBuilder
instead.
Conclusion
Overall I think this new approach to 3rd party DI integration is much nicer for the application developer.
I actually thought the way this feature was introduced was pretty interesting, so I went digging through the code in GitHub. If you'd like to see where some of this black magic happens, take a look at the StartupLoader class in the Hosting repo.