AutofacServiceProviderFactory in ASP.NET Core - Problems With Dependency Injection (Part 1)

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:

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.


What's In This Article: AutofacServiceProviderFactory in ASP.NET Core

Remember to check out these platforms:

// FIXME: social media icons coming back soon!


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:

https://www.devleader.ca/2024/03/19/autofac-in-asp-net-core-how-to-avoid-this-debugging-nightmare

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 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.
  • With that said, we have access to the IConfiguration instance from the WebApplicationBuilder 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 on WebApplication. 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 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.
  • 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 the WebApplication 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 on WebApplication 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!