AutofacServiceProviderFactory in ASP.NET Core – Problems With Dependency Injection (Part 1)
Dev Leader
Posted on May 7, 2024
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 of ASP.NET Core. I'll be sure to include the series below as the issues are published:
Part 1: AutofacServiceProviderFactory in ASP.NET Core - Problems With Dependency Injection
Part 2: Autofac ContainerBuilder In ASP.NET Core – What You Need To Know
Part 3: Autofac ComponentRegistryBuilder in ASP.NET Core - How To Register Dependencies
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 be even more content for you to explore. Keep your eyes peeled on my Dometrain courses for a more guided approach to these topics later in 2023.
Dependency Injection: A Primer
What is Dependency Injection
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!
What is Autofac?
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.
What is the AutofacServiceProviderFactory in ASP.NET Core?
The AutofacServiceProviderFactory
is a specific component in the Autofac library designed for integrating 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’ve written about this before in this article:
Exploring A Sample ASP.NET Core Application
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 more clear if you were to go 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:
What Can We Get With AutofacServiceProviderFactory in ASP.NET Core?
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:
We have access to the
WebApplicationBuilder
on the AutofacContainerBuilder
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.With that said, we have access to the
IConfiguration
instance from theWebApplicationBuilder
because it’s exposed on the web app builder itself.We get the ability to resolve dependencies from the container that are defined directly on our minimal APIs! In the example I shared above, the dependency classes A through C are all types that can be resolved from the container automatically on the minimal API. There’s a catch for one of these which we’ll cover, but the point is their registrations can be seen by our minimal API.
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 for the style of development that I like to do.
What’s Missing With AutofacServiceProviderFactory in ASP.NET Core?
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:
The
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 onWebApplication
. This is because this instance is never registered onto the container and therefore cannot be automatically injected for us.We can’t overcome this behavior by calling the
Build()
method manually on theWebApplicationBuilder
inside of an Autofac registration. This is because the chain of registrations executes once we callBuild()
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.Because we cannot resolve the
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 theWebApplication
instance is not accessible to us via the container.Based on the above points, we cannot have dependencies on the routes like
DependencyB
in the example above. This is because this type has a dependency onWebApplication
and the container simply does not know about it. In the 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 that builds mostly plugin architecture applications, this is very limiting for me!
Wrapping Up AutofacServiceProviderFactory in ASP.NET Core
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! Meet other like-minded software engineers and join my Discord community!
Want More Dev Leader Content?
- Follow along on this platform if you haven’t already!
- Subscribe to my free weekly software engineering and dotnet-focused newsletter. I include exclusive articles and early access to videos: SUBSCRIBE FOR FREE
- Looking for courses? Check out my offerings: VIEW COURSES
- E-Books & other resources: VIEW RESOURCES
- Watch hundreds of full-length videos on my YouTube channel: VISIT CHANNEL
- Visit my website for hundreds of articles on various software engineering topics (including code snippets): VISIT WEBSITE
- Check out the repository with many code examples from my articles and videos on GitHub: VIEW REPOSITORY
Posted on May 7, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 13, 2024