Autofac ContainerBuilder in ASP.NET Core - What You Need To Know (Part 2)

There are many ways to manage dependency injection inside our applications, and I think it's important to understand the benefits and limitations of different approaches. Using an Autofac ContainerBuilder in ASP.NET Core as the primary way to structure your dependencies instead of using the suggested AutofacServiceProviderFactory is one such path we can explore!

In this article, I highlight how to use Autofac's ContainerBuilder in your ASP.NET Core application instead of the AutofacServiceProviderFactory. We'll look at what you can and cannot do with this approach, versus the other approaches we have access to with dependency injection.

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: Autofac ContainerBuilder in ASP.NET Core

Remember to check out these platforms:

// FIXME: social media icons coming back soon!


The Problem With AutofacServiceProviderFactory

To be fair, the section title is *almost* click-bait. I do think that AutofacServiceProviderFactory being used as the suggested way to set up Autofac in your ASP.NET Core applications is great for most applications. The great majority of developers who want to use Autofac as their dependency injection framework of choice would not run into many issues at all this way.

It does afford us the ability to:

  • Access the WebApplicationBuilder (and anything available at this point in time)
  • Access to the instance of IConfiguration (also available off the WebApplicationBuilder instance)
  • Ability to pass dependencies onto minimal APIs

But the big issue for me: We can't access the WebApplication instance. When I build plugin architectures in C#, in particular building ASP.NET Core applications, I like to have access to the WebApplication instance in order to register routes. This allows me to register minimal APIs from my plugins with ease, which technically only need access to an implementation of IEndpointRouteBuilder to get the handy syntax.

Can I register non-minimal APIs without this? Absolutely. Is there another way to provide similar syntax and not require a WebApplication instance? Very likely. But instead of trying to work around THAT problem, I wanted to see if I could just get access to the dependency I am interested in.

It was time to change the plan on how to set up my dependency container!


Exploring A Sample ASP.NET Core Application

Let's look at a sample application so that we have some common ground to explore. If you've read the previous article, this will look similar -- a variation of the sample weather application:

using Autofac;

using Microsoft.AspNetCore.Mvc;

// personal opinion:
// I absolutely love having the entry point of my
// applications being essentially:
// - make my dependencies
// - give me the primary dependency
// - use it
// - ... nothing else :)
ContainerBuilder containerBuilder = new();
containerBuilder.RegisterModule<MyModule>();

using var container = containerBuilder.Build();
using var scope = container.BeginLifetimeScope();
var app = scope.Resolve<WebApplication>();
app.Run();

internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

internal sealed class MyModule : Module
{
    protected override void Load(ContainerBuilder containerBuilder)
    {
        containerBuilder.RegisterType<DependencyA>().SingleInstance();
        containerBuilder.RegisterType<DependencyB>().SingleInstance();
        containerBuilder.RegisterType<DependencyC>().SingleInstance();
        containerBuilder
            .Register(ctx =>
            {
                var builder = WebApplication.CreateBuilder(Environment.GetCommandLineArgs());
                return builder;
            })
            .SingleInstance();
        containerBuilder
            .Register(ctx => ctx.Resolve<WebApplicationBuilder>().Configuration)
            .As<IConfiguration>()
            .SingleInstance();
        containerBuilder
            .Register(ctx =>
            {
                var builder = ctx.Resolve<WebApplicationBuilder>();

                var app = builder.Build();
                app.UseHttpsRedirection();

                // FIXME: the problem is that the Autofac ContainerBuilder
                // was used to put all of these pieces together,
                // but we never told the web stack to use Autofac as the
                // service provider.
                // this means that the minimal API will never be able to
                // find services off the container. we would need to resolve
                // them BEFORE the API is called, like in this registration
                // method itself, from the context that is passed in.
                //DependencyA dependencyA = ctx.Resolve<DependencyA>();

                // FIXME: But... What happens if something wants to take a
                // dependency on the WebApplication instance itself? Once the
                // web application has been built, there's no more adding
                // dependencies to it!

                var summaries = new[]
                {
                    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
                };

                app.MapGet(
                    "/weatherforecast",
                    (
                        [FromServices] DependencyA dependencyA
                      , [FromServices] DependencyB dependencyB
                      , [FromServices] DependencyC dependencyC
                    ) =>
                    {
                        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;
                    });

                return app;
            })
            .SingleInstance();
    }
}

internal sealed class DependencyA(
    WebApplicationBuilder _webApplicationBuilder);

internal sealed class DependencyB(
    WebApplication _webApplication);

internal sealed class DependencyC(
    IConfiguration _configuration);

One callout of this approach is that using the Autofac ContainerBuilder class as our primary dependency container affords us the opportunity to structure our entry point to just:

  • Container creation
  • Dependency registration
  • Primary dependency resolution
  • ... Call a single method to start the app up!

This is, in my opinion, ideal application startup code. Why? Because you never need to come back here to touch it. Ever. No matter how many things you add in! And that's all because you can scan assemblies to load more modules.

Again, this is a personal preference of mine and I am not trying to claim this should be everyone's goal.

The Flaws of Autofac ContainerBuilder in ASP.NET Core

Of course, another approach that isn't quite bulletproof. So let's discuss what we DON'T get with this setup of Autofac:

  • Service-resolved parameters passed on minimal APIs simply don't work. The WebApplication that was built is not configured to use Autofac as the dependency injection framework!
  • Like the previous article, we still can't get the WebApplication instance on the dependency container... So we didn't make any advancements specifically on accessing that.

But that's mostly it! It's not a terrible list of drawbacks, but the Autofac ContainerBuilder approach was not a silver bullet solution for us. So what did we get out of it? This video on Autofac will also help explain:

The Benefits of Autofac ContainerBuilder in ASP.NET Core

Pros and cons for everything we do! Now that we've seen the issues with Autofac ContainerBuilder in ASP.NET Core, it's time to look at what advancements we got out of this:

  • We can still access the WebApplicationBuilder and IConfiguration instances, so that's a comparable benefit to using the AutofacServiceProviderFactory approach.
  • We can get a very streamlined entry point to our program, which I really like to see. Container creation, registration, resolve your entry point method, and that's all!
  • Minimal APIs work, but not with dependencies. Still, we can pre-resolve the dependencies the minimal APIs want and pass those in at the time of method registration. See the commented code!

More Inversion for Plugin-Based ASP.NET Routes?

We saw that we could register minimal APIs within an Autofac registration method, but unfortunately, we cannot resolve dependencies from the container directly on the minimal API call itself. We could go build a dedicated class like the following that handles route definitions with dependencies being resolved automatically:

internal sealed class WeatherForecastRoutes(
    DependencyA _dependencyA
    // FIXME: still can't depend on this because
    // we can't get the WebApplication
//, DependencyB _dependencyB 
  , DependencyC _dependencyC)
{
    private static readonly string[] _summaries = new[]
    {
        "Freezing", "Bracing", // ...
    };

    public WeatherForecast[] Forecast()
    {
        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;
    }
}

The automatic resolution happens if we have this class AND the dependencies all on the same container. Then it's just a matter of calling this code:

var weatherForecastRoutes = ctx.Resolve<WeatherForecastRoutes>();
app.MapGet("/weatherforecast2", weatherForecastRoutes.Forecast);

This still sucks a bit from a plugin perspective because we'd need to go resolve all of the route classes manually just to call the registration code like that -- all stemming from the fact that these things can't resolve their own access to the WebApplication instance.

But wait... What if we flip things around? What if we could resolve some interface like IRegisterRoutes and pass in the WebApplication instance?!

// NOTE: make sure to register WeatherForecastRouteRegistrar on the
// autofac container as IRegisterRoutes!

internal interface IRegisterRoutes
{
    void RegisterRoutes(WebApplication app);
}

internal sealed class WeatherForecastRouteRegistrar(
    WeatherForecastRoutes _weatherForecastRoutes) :
    IRegisterRoutes
{
    public void RegisterRoutes(WebApplication app)
    {
        app.MapGet("/weatherforecast2", _weatherForecastRoutes.Forecast);
    }
}

// TODO: add this to the autofac code where the 
// WebApplication instance is built:
foreach (var registrar in ctx.Resolve<IEnumerable<IRegisterRoutes>>())
{
    registrar.RegisterRoutes(app);
}

Now we don't even need to care if the WebApplication instance is accessible to our plugins! Maybe the first version wasn't so limiting after all? Maybe we're onto something here... But the next article should explain this in more detail.


Wrapping Up Autofac ContainerBuilder in ASP.NET Core

In this article, I explored using an Autofac ContainerBuilder explicitly instead of using AutofacServiceProviderFactory as is normally suggested. We saw some similar benefits and drawbacks, but also a different set of things to consider. Each way can offer pros and cons depending on what you're after in your application.

What was interesting was that if we're trying to work towards plugins, we might not even need to access the WebApplication instance from our plugins at all! If we care about minimal APIs, this might still be limiting... but otherwise, we're onto an interesting line of thinking!

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!