We have plenty of awesome options for dependency injection when working in ASP.NET Core applications. For the most part, if you’re not building anything super complicated concerning your types or your software architecture, you can get by with the built-in IServiceCollection. However, we can use AutofacServiceProviderFactory in ASP.NET Core to make Autofac our dependency container of choice in our app!
In this article, I highlight how to use AutofacServiceProviderFactory in your ASP.NET Core application along with what you can and cannot do with it. Having many options for dependency injection means that we have many pros and cons to analyze!
This will be part of a series where I explore dependency resolution with Autofac inside ASP.NET Core. I'll be sure to include the series below as the issues are published:
At the end of this series, you'll be able to more confidently explore plugin architectures inside of ASP.NET Core and Blazor, which will provide even more content for you to explore.
Dependency Injection (DI) is a design pattern used in programming to make software systems easier to develop, test, and maintain. In C#, like in many other programming languages, dependency injection helps to decouple the components of your applications. Ideally, this leads to developing software that is more flexible and extensible.
Dependency Injection is about removing the hard-coded dependencies and making it possible to change them, either at runtime or compile time. This can be useful for many reasons, such as allowing a program to use different databases or testing components by replacing them with mock objects.
Consider a code example where we create an instance of a car with an engine:
public sealed class Car
{
private readonly IEngine _engine;
public Car()
{
// The Car class directly depends on the GasEngine class.
_engine = new GasEngine();
}
}
We can instead “inject” the dependency via the constructor:
public sealed class Car
{
private readonly IEngine _engine;
// The engine is injected into the car via the constructor
// so now there is no direct dependency on the Engine class,
// but there is a dependency on the IEngine interface
// which has nothing to do with the implementation
public Car(IEngine engine)
{
_engine = engine;
}
}
We can even use the idea of a “Dependency Container” that helps make this process seem a bit more magical by not requiring us to explicitly create instances of objects by passing in dependencies. Instead, the container allows us to resolve these dependencies. More on that in the next section!
Autofac is a popular inversion of control (IoC) container for .NET. It manages the dependencies between classes by injecting instances where needed, thereby facilitating a more modular and testable codebase. Autofac is used extensively in applications to implement the dependency injection pattern, allowing us to write cleaner, more maintainable code for the reasons I explained in the previous section.
There are other IoC containers to help us manage and resolve dependencies in our applications, including the built-in IServiceCollection, but Autofac is the one that I am most comfortable using. As .NET has evolved IServiceCollection has become more feature-rich, and with the gap in features between the two closing, Autofac is still one that I like using in my development.
The AutofacServiceProviderFactory
is a specific component in the Autofac library designed to integrate Autofac with the built-in dependency injection (DI) system in ASP.NET Core. Essentially, it acts as a bridge, allowing you to use Autofac as the DI container instead of the default one provided by Microsoft.
I wanted to make sure we had a common application to refer to when I get into more of the technical details of what we do and do not get with AutofacServiceProviderFactory
. The following code is the sample weather app we get from Visual Studio when creating a new ASP.NET Core web API, but I’ve modified it slightly to showcase some of the behavior we get with AutofacServiceProviderFactory
:
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory(containerBuilder =>
{
containerBuilder
.RegisterInstance(builder)
.SingleInstance();
containerBuilder
.Register(ctx => ctx.Resolve<WebApplicationBuilder>().Configuration)
.SingleInstance();
// FIXME: we can't do this because the WebApplicationBuilder
// the WebApplicationBuilder is responsible for building the
// WebApplication, so we can't do that manually just to add
// it into the container
//containerBuilder
// .Register(ctx =>
// {
// var app = ctx.Resolve<WebApplicationBuilder>().Build();
// app.UseHttpsRedirection();
// return app;
// })
// .SingleInstance();
containerBuilder.RegisterType<DependencyA>().SingleInstance();
containerBuilder.RegisterType<DependencyB>().SingleInstance();
containerBuilder.RegisterType<DependencyC>().SingleInstance();
//containerBuilder
// .RegisterBuildCallback(ctx =>
// {
// // FIXME: this was never registered
// var app = ctx.Resolve<WebApplication>();
// var summaries = new[]
// {
// "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
// };
// app.MapGet(
// "/weatherforecast",
// (
// [FromServices] DependencyA dependencyA // this will work
// , [FromServices] DependencyB dependencyB // FIXME: this will fail!!
// , [FromServices] DependencyC dependencyC // this will work
// ) =>
// {
// var forecast = Enumerable.Range(1, 5).Select(index =>
// new WeatherForecast
// (
// DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
// Random.Shared.Next(-20, 55),
// summaries[Random.Shared.Next(summaries.Length)]
// ))
// .ToArray();
// return forecast;
// });
// });
}));
// FIXME: we can't get the WebApplication into the
// Autofac container, because it's already been built.
// this means if we have anything that wants to take a
// dependency on the WebApplication instance itself, we
// can't resolve it from the container.
var app = builder.Build();
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet(
"/weatherforecast",
(
[FromServices] DependencyA dependencyA // this will work
, [FromServices] DependencyB dependencyB // FIXME: this will fail!!
, [FromServices] DependencyC dependencyC // this will work
) =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
});
app.Run();
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
internal sealed class DependencyA(
WebApplicationBuilder _webApplicationBuilder);
internal sealed class DependencyB(
Lazy<WebApplication> _webApplication);
internal sealed class DependencyC(
IConfiguration _configuration);
You should note that I’ve modified the weather API itself to take in 3 dependencies that we want to resolve from the service list. [FromService]
is, in fact, not required here, but it makes the error messages clearer if you were to run this and want to understand where and why it fails.
But wait… why does it fail?! Keep on readin’ and follow along with this video on Autofac to find out more:
Let’s start off with what we get from this setup because I do think that this is the typical path. To be clear, there’s nothing “wrong” with this approach, but you need to understand where dependencies are registered and resolved, and, therefore what works with your container:
WebApplicationBuilder
on the Autofac ContainerBuilder
instance. This allows us to have services depending on the app builder instance, which means we can have modules and/or plugins that want to setup information on the app builder or otherwise read state from the app builder.IConfiguration
instance from the WebApplicationBuilder
because it’s exposed on the web app builder itself.
In general, this is probably “good enough” for most situations if you just want to use Autofac for your ASP.NET Core application. However, this is limiting to the style of development that I like to do.
Now that we’ve seen the goodness that we get, let’s discuss where there are some drawbacks. They’re essentially already highlighted in the code with FIXME comments, but it’s worth elaborating on them in more detail here. Again, this is not to suggest this is the “wrong” way to do it, just that you have some considerations to make:
WebApplication
instance is not something that we can resolve from the container. That is, if you ever want to have classes automatically resolve from the dependency container, they cannot take a dependency on WebApplication
. This is because this instance is never registered onto the container and, therefore, cannot be automatically injected for us.Build()
method manually on the WebApplicationBuilder
inside of an Autofac registration. This is because the chain of registrations executes once we call Build()
on the web application builder OUTSIDE of the container, which then handles the rest of the application being built. Said another way, this creates a bit of a circular dependency on the responsibilities that need to be handled.WebApplication
instance from the dependency container, we cannot create plugins that add their own routes to the application using the minimal API route registration syntax. If this is indeed possible to do, it would have to be using a different API and instance of a different type since the WebApplication
instance is not accessible to us via the container.DependencyB
in the example above. This is because this type has a dependency on WebApplication
and the container simply does not know about it. In future articles, you’ll see examples of this pattern coming up again, so it’s worth mentioning in this article for reference.
Many of these are not a concern for folks building typical applications. However, as someone who builds mostly plugin architecture applications, this is very limiting for me!
In this article, I provided a brief overview of dependency injection and Autofac within ASP.NET Core. The primary takeaway was looking at what you do and do not get when using AutofacServiceProviderFactory in ASP.NET Core. While the limitations of this are minimized for the average application, this does not work well for a plugin architecture that wants to extend the API routes via plugins.
If you found this useful and you’re looking for more learning opportunities, consider subscribing to my free weekly software engineering newsletter and check out my free videos on YouTube!