Why AutoFac

I’ve frequently seen a question arise about why anyone would use an open-source dependency injection framework like AutoFac when Microsoft provides Microsoft.Extensions.DependencyInjection (MEDI). Isn’t that good enough?

It’s a fair question. MEDI is a capable dependency injection container and will support most application needs. However, I still find it worthwhile to plug AutoFac in.

Microsoft created MEDI so that framework and library authors could depend on some basic dependency injection capabilities without writing adapters for every popular DI container. Microsoft specified base-level functional expectations, but application developers that wanted more power could use a different tool as long as it could conform to the protocol Microsoft specified. As a result, the MEDI provides a limited feature set by design.

I frequently want more robust capabilities and will install AutoFac to get them.

The rest of this post will highlight some of the features of AutoFac that I particularly like. While not provided out of the box, Developers can emulate many of these features with MEDI, though perhaps clumsily. Some have no analog at all. Sample code is also on GitHub.

Assembly Scanning

AutoFac has built-in support for assembly scanning: finding all the services that match specific criteria and automatically registering them. Used tactically, this can reduce a lot of boilerplate in your Startup.cs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Fact]
public void AutoFac_ScanAndFilter()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(AssemblyScanning).Assembly)
.PublicOnly()
.Where(t => t.Namespace == "WhyAutoFac.AssemblyScanningDemo")
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();

var container = builder.Build();
var repository = container.Resolve<ISampleRepository>();
Assert.IsType<SampleRepository>(repository);
}

You have to write it yourself in MEDI, though it is relatively easy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[Fact]
public void MEDI_ScanItYourself()
{
var services = new ServiceCollection();

var repositories = typeof(AssemblyScanning).Assembly
.GetTypes()
.Where(t => t.IsPublic)
.Where(t => t.Namespace == "WhyAutoFac.AssemblyScanningDemo")
.Where(t => t.Name.EndsWith("Repository"));

foreach (var repositoryType in repositories)
{
foreach (var interfaceType in repositoryType.GetInterfaces())
{
services.AddScoped(interfaceType, repositoryType);
}
}

var serviceProvider = services.BuildServiceProvider();
var repository = serviceProvider.GetService<ISampleRepository>();
Assert.IsType<SampleRepository>(repository);

}

Modules

AutoFac lets you break out your registrations and group them in Module classes. You can pass arguments to the module constructor to further configure the registrations.

1
2
3
4
5
6
7
8
9
10
[Fact]
public void AutoFac_CanRegisterModules()
{
var builder = new ContainerBuilder();
builder.RegisterModule<FeatureModule>();
var container = builder.Build();

var service = container.Resolve<IService>();
Assert.IsType<Service>(service);
}

You can approximate this feature using extension methods in MEDI.

1
2
3
4
5
6
7
8
9
10
[Fact]
public void MEDI_CanDoModulesViaExtensionMethods()
{
var services = new ServiceCollection();
services.AddFeatureModule();
var serviceProvider = services.BuildServiceProvider();

var service = serviceProvider.GetService<IService>();
Assert.IsType<Service>(service);
}

However, AutoFac modules can be discovered and registered through assembly scanning, while extension methods cannot.

Register the same component as multiple services.

I use this feature a good bit. I often find myself with a stateful service that I want to expose under multiple interfaces. I need to resolve the same instance, no matter what interface the developer used to inject it.

For example, imagine a context provider pattern: a middleware wants to set some request-specific context, and services later in the request need to read that context. So there’s a write interface and a read interface, both different views into the same class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface IContextResolver
{
int GetTenantId();
}

interface IContextProvider
{
void SetTenantId(int id);
}

class ContextHolder : IContextProvider, IContextResolver
{
private int _tenantId;
public void SetTenantId(int id) => _tenantId = id;
public int GetTenantId() => _tenantId;
}

AutoFac makes it trivial to register a class that implements both interfaces and makes sure it always resolves the same instance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Fact]
public void AutoFac_RegistersSameInstanceUnderBothServices()
{
var builder = new ContainerBuilder();
builder.RegisterType<ContextHolder>()
.As<IContextProvider>()
.As<IContextResolver>()
.InstancePerLifetimeScope();

var container = builder.Build();
var contextProvider = container.Resolve<IContextProvider>();
var contextResolver = container.Resolve<IContextResolver>();
Assert.Same(contextProvider, contextResolver);
}

You can do this in MEDI, but the naive registration does not work as you’d expect.

1
2
3
4
5
6
7
8
9
10
11
12
[Fact]
public void MEDI_DoesNotRegisterSameInstance()
{
var services = new ServiceCollection();
services.AddScoped<IContextProvider, ContextHolder>();
services.AddScoped<IContextResolver, ContextHolder>();
var serviceProvider = services.BuildServiceProvider();

var contextProvider = serviceProvider.GetService<IContextProvider>();
var contextResolver = serviceProvider.GetService<IContextResolver>();
Assert.NotSame(contextProvider, contextResolver);
}

MEDI treats different interfaces as different instances. You’ll need to do annoying work with lambdas to ensure resolving the same instance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Fact]
public void MEDI_YouCanGetSameInstancesViaLambdas()
{

var services = new ServiceCollection();
services.AddScoped<ContextHolder>();
services.AddScoped<IContextProvider>(c => c.GetRequiredService<ContextHolder>());
services.AddScoped<IContextResolver>(c => c.GetRequiredService<ContextHolder>());
var serviceProvider = services.BuildServiceProvider();

var contextProvider = serviceProvider.GetService<IContextProvider>();
var contextResolver = serviceProvider.GetService<IContextResolver>();
Assert.Same(contextProvider, contextResolver);
}

Improved Reflection Registration

AutoFac has a few convenient utilities for reflection-based registrations that don’t require me to drop down to using lambdas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Fact]
public void AutoFac_CanProvidePrimitivesAndResolveTheRest()
{
var builder = new ContainerBuilder();
builder.RegisterType<Logger>()
.AsImplementedInterfaces()
.SingleInstance();
builder.RegisterType<Repository>()
.WithParameter("connectionString", "Server=.;Database=WhyAutoFac;Trusted_Connection=True;")
.AsImplementedInterfaces()
.InstancePerLifetimeScope();

var container = builder.Build();
var repository = container.Resolve<IRepository>();
Assert.IsType<Repository>(repository);
}

Also, I love AsImplementedInterfaces()!

In MEDI, you have to write manual lambda expressions when you want to inject primitives.

1
2
3
4
5
6
7
8
9
10
11
[Fact]
public void MEDI_HasToUseLambda()
{
var services = new ServiceCollection();
services.AddSingleton<ILogger, Logger>();
services.AddScoped<IRepository>(c => new Repository("Server=.;Database=WhyAutoFac;Trusted_Connection=True;", c.GetRequiredService<ILogger>()));

var serviceProvider = services.BuildServiceProvider();
var repository = serviceProvider.GetService<IRepository>();
Assert.IsType<Repository>(repository);
}

Lambdas get verbose with more than a couple of dependencies. If I add a new dependency in the future, I’ll have to update the lambda. AutoFac will continue to work.

There are other workarounds: for example, using IOptions to avoid passing primitives into your service constructors. But sometimes, I want the simplicity of a string!

Override Services at a Scope Level

I run into this in integration tests. I want to consume my standard app-level registrations, but when I start a scope for an integration test, I might want to swap a service out with a different one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[Fact]
public void AutoFac_CanOverrideServicesAtScopeLevel()
{
// imagine this is in the real app Startup.cs
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<EmailSender>()
.AsImplementedInterfaces()
.InstancePerLifetimeScope();

var container = containerBuilder.Build();

// now in my test I can override the registration
var testScope = container.BeginLifetimeScope(builder =>
{
builder.RegisterType<TestEmailSender>()
.AsSelf()
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
});

// and the test scope will use the test implementation
var emailSender = testScope.Resolve<IEmailSender>();
Assert.IsType<TestEmailSender>(emailSender);
emailSender.Send("Hello");

// I can also resolve the same instance under its real concrete type because I used AsSelf()
// and can now access properties specific to the test double
var testSender = testScope.Resolve<TestEmailSender>();
Assert.Single(testSender.SentMessages);
Assert.Equal("Hello", testSender.SentMessages[0]);
}

MEDI can’t do this at all. Once a ServiceProvider is built, you cannot modify it. You can design around this by putting hooks in your Startup.cs to let you override types at the global level, but that will only work for some scenarios.

Named Scopes

In some integration tests, I simulate multiple web requests during the test. Each request needs its own per-request scope, but I also want a lifetime scope that wraps around the entire test. I can’t use an app-wide singleton because that would leak data between tests and is a cause of race conditions.

Inside that test scope, I want to start and stop additional child scopes that represent individual requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[Fact]
public void AutoFac_NamedScope()
{
// imagine all this is in some global test setup area
var builder = new ContainerBuilder();
builder.RegisterType<UnitOfWork>()
.AsImplementedInterfaces()
.InstancePerLifetimeScope();

builder.RegisterType<Request1>().AsSelf();
builder.RegisterType<Request2>().AsSelf();

// I dont want this to be a singleton, because I want it to be disposed at the end of
// and not leak data between tests
builder.RegisterType<TestEventPublisher>()
.InstancePerMatchingLifetimeScope("CURRENT_TEST")
.AsImplementedInterfaces()
.AsSelf();

var container = builder.Build();

using (var testScope = container.BeginLifetimeScope("CURRENT_TEST"))
{
// we want each request to run in its own scope because that's how the real app works
using (var request1Scope = testScope.BeginLifetimeScope())
{
request1Scope.Resolve<Request1>().DoSomething();
}

using (var request2Scope = testScope.BeginLifetimeScope())
{
request2Scope.Resolve<Request2>().DoSomething();
}

var testEventPublisher = testScope.Resolve<TestEventPublisher>();
Assert.Equal(2, testEventPublisher.PublishedEvents.Count);
}
}

MEDI also can’t do this. It only supports singleton and scoped, and scopes are not nested.

Registering Func

Sometimes a service is expensive to initialize, and I might only need it some of the time. In that situation, I’d like to inject a Lazy<T> or a Func<T> to defer building that service unless and until needed.

These both work out of the box with AutoFac.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MightNotNeedExpensiveService
{
private readonly Func<IExpensiveService> _expensiveServiceFactory;

public MightNotNeedExpensiveService(Func<IExpensiveService> expensiveServiceFactory)
{
_expensiveServiceFactory = expensiveServiceFactory;
}

public void MaybeUseService()
{
if (Random.Shared.NextDouble() > 0.5)
{
var expensiveService = _expensiveServiceFactory();
// do something with expensive service
}
}
}

[Fact]
public void AutoFac_AutomaticallySupportsFuncT()
{
var builder = new ContainerBuilder();
builder.RegisterType<ExpensiveService>().As<IExpensiveService>();
builder.RegisterType<MightNotNeedExpensiveService>();
var container = builder.Build();

container.Resolve<MightNotNeedExpensiveService>();
}

But not in MEDI, though you can do it manually.

1
2
3
4
5
6
7
8
9
10
11
12
13
[Fact]
public void MEDI_ButYouCanDoItManually()
{

var services = new ServiceCollection();
services.AddScoped<IExpensiveService, ExpensiveService>();
services.AddScoped<MightNotNeedExpensiveService>();
services.AddSingleton<Func<IExpensiveService>>(c => new Func<IExpensiveService>(
() => c.GetRequiredService<IExpensiveService>()));
var serviceProvider = services.BuildServiceProvider();

serviceProvider.GetService<MightNotNeedExpensiveService>();
}

Manual Lifetime Management

In a desktop app, I needed to kick off a long job that used EntityFramework. I did not want to run the entire job in a single DbContext unit of work; instead, I wanted to be able to create and dispose of DbContext multiple times manually.

AutoFac let me do that with Func<Owned<T>>.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class DbContext : IDisposable
{
public static int DisposeCount = 0;

public void Dispose() => DisposeCount++;

public void SaveChanges(){}
}

class BigImportJob
{
private readonly Func<Owned<DbContext>> _getUnitOfWork;

public BigImportJob(Func<Owned<DbContext>> getUnitOfWork)
{
_getUnitOfWork = getUnitOfWork;
}

public void Run()
{
for (int i = 0; i < 100; i++)
{
using var db = _getUnitOfWork();
// do stuff with it
db.Value.SaveChanges();
}
}
}

[Fact]
public void AutoFac_CanRegisterStuff()
{
var builder = new ContainerBuilder();

builder.RegisterType<DbContext>()
.InstancePerLifetimeScope()
.AsSelf();

builder.RegisterType<BigImportJob>()
.AsSelf();

var container = builder.Build();

using var scope = container.BeginLifetimeScope();

var job = scope.Resolve<BigImportJob>();
job.Run();

// Even though dbcontext is instance per scope, we can still use it multiple times in the job
Assert.Equal(100, DbContext.DisposeCount);
}

This pattern is conceptually the same as starting a child scope each time you invoke the Func.

You can approximate this in MEDI by using additional scopes. Inject IScopeProvider and begin and end scopes yourself.

Decorator

AutoFac has built-in support for registering decorators. The nice thing is that the decorator will obey the scope of the decorated service.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 [Fact]
public void AutoFac_HasFirstClassDecoratorSupport()
{
var builder = new ContainerBuilder();
builder.RegisterType<SaveHandler>()
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterDecorator<LoggingDecorator, IHandler>();

var container = builder.Build();

var handler = container.Resolve<IHandler>();
var asLoggingDecorator = Assert.IsType<LoggingDecorator>(handler);
Assert.IsType<SaveHandler>(asLoggingDecorator.Inner);
}

You can do this in MEDI, but you have to use the verbose and brittle lambda syntax.

1
2
3
4
5
6
7
8
9
10
11
12
13
[Fact]
public void MEDI_CanDoItManually()
{
var services = new ServiceCollection();
services.AddScoped<SaveHandler>();
services.AddScoped<IHandler>(ctx => new LoggingDecorator(ctx.GetRequiredService<SaveHandler>()));

var serviceProvider = services.BuildServiceProvider();

var handler = serviceProvider.GetRequiredService<IHandler>();
var asLoggingDecorator = Assert.IsType<LoggingDecorator>(handler);
Assert.IsType<SaveHandler>(asLoggingDecorator.Inner);
}

Keyed services

Sometimes you have multiple implementations of an interface, and need to inject particular flavors into particular targets. AutoFac can do this with Keyed services.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Fact]
public void AutoFac_CanRegisterUnderKeys()
{
var builder = new ContainerBuilder();
builder.RegisterType<GooglePushNotifier>()
.Keyed<IPushNotifier>("google");
builder.RegisterType<ApplePushNotifier>()
.Keyed<IPushNotifier>("apple");

var container = builder.Build();
var apple = container.ResolveKeyed<IPushNotifier>("apple");
var google = container.ResolveKeyed<IPushNotifier>("google");

Assert.IsType<ApplePushNotifier>(apple);
Assert.IsType<GooglePushNotifier>(google);
}

If you need the target class to define which flavor it needs injected, you can use [KeyFilter].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class GooglePushConsumer
{
public IPushNotifier Notifier { get; }

public GooglePushConsumer([KeyFilter("google")]IPushNotifier notifier)
{
Notifier = notifier;
}
}

[Fact]
public void AutoFac_CanAskForKeyedInstance()
{
var builder = new ContainerBuilder();
builder.RegisterType<GooglePushNotifier>()
.Keyed<IPushNotifier>("google");
builder.RegisterType<ApplePushNotifier>()
.Keyed<IPushNotifier>("apple");
builder.RegisterType<GooglePushConsumer>()
.WithAttributeFiltering();

var container = builder.Build();
var googleConsumer = container.Resolve<GooglePushConsumer>();
Assert.IsType<GooglePushNotifier>(googleConsumer.Notifier);
}

It’s a bit of a code-smell for a class to ask for particular implementations of its interface, though. I’d instead handle that at registration time. You can do this through the lambda syntax or parameter resolution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Fact]
public void AutoFac_CanProvideKeyedInstanceThroughRegistration()
{
var builder = new ContainerBuilder();
builder.RegisterType<GooglePushNotifier>()
.Keyed<IPushNotifier>("google");
builder.RegisterType<ApplePushNotifier>()
.Keyed<IPushNotifier>("apple");
builder.RegisterType<GooglePushConsumer>()
.WithParameter(new ResolvedParameter(
(p, _) => p.ParameterType == typeof(IPushNotifier),
(_, ctx) => ctx.ResolveKeyed<IPushNotifier>("google")));

var container = builder.Build();
var googleConsumer = container.Resolve<GooglePushConsumer>();
Assert.IsType<GooglePushNotifier>(googleConsumer.Notifier);
}

MEDI doesn’t have keyed services, yet. It’s coming in .NET 8, though. For the situations we’ve seen so far, you can get around the lack of keyed services by registering the concrete types and then resolving them in the lambda registrations.

1
2
3
4
5
6
7
8
9
10
11
12
13
[Fact]
public void MEDI_CanProvideParticularImplementationsAtRegistrationTime()
{
var services = new ServiceCollection();
services.AddTransient<GooglePushNotifier>();
services.AddTransient<ApplePushNotifier>();
services.AddTransient<GooglePushConsumer>(ctx =>
new GooglePushConsumer(ctx.GetRequiredService<GooglePushNotifier>()));

var servicesProvider = services.BuildServiceProvider();
var googleConsumer = servicesProvider.GetRequiredService<GooglePushConsumer>();
Assert.IsType<GooglePushNotifier>(googleConsumer.Notifier);
}

Another place keyed services are helpful is in implementing a strategy pattern, where I can’t know which implementation I need until runtime. AutoFac can help with the IIndexed type, allowing me to inject a Dictionary that maps keys to service implementations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class NotificationService
{
public IIndex<string, IPushNotifier> Strategies { get; }

public NotificationService(IIndex<string, IPushNotifier> strategies)
{
Strategies = strategies;
}

public void Notify(string platform, string message)
{
Strategies[platform].Notify(message);
}
}

[Fact]
public void AutoFac_CanUseIIndexToDoStrategyPattern()
{
var builder = new ContainerBuilder();
builder.RegisterType<GooglePushNotifier>()
.Keyed<IPushNotifier>("google");
builder.RegisterType<ApplePushNotifier>()
.Keyed<IPushNotifier>("apple");
builder.RegisterType<NotificationService>();

var container = builder.Build();
var service = container.Resolve<NotificationService>();
service.Notify("google", "Hello, Google!");
service.Notify("apple", "Hello, Apple!");
}

In MEDI, I have to push the key into the service itself, resolve all service implementations, and search for the one I need by name.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class GooglePushStrategy : IPushStrategy
{
public string Name => "google";
public void Notify(string message)
{
Console.WriteLine($"Google: {message}");
}
}

class ApplePushStrategy : IPushStrategy
{
public string Name => "apple";
public void Notify(string message)
{
Console.WriteLine($"Apple: {message}");
}
}

class MediPushService
{
public IEnumerable<IPushStrategy> Strategies { get; }

public MediPushService(IEnumerable<IPushStrategy> strategies)
{
Strategies = strategies;
}

public void Notify(string platform, string message)
{
Strategies.First(s => s.Name == platform).Notify(message);
}
}

[Fact]
public void MEDI_CanDoStrategyPatternMoreExplicitly()
{
var services = new ServiceCollection();
services.AddTransient<IPushStrategy, GooglePushStrategy>();
services.AddTransient<IPushStrategy, ApplePushStrategy>();
services.AddTransient<MediPushService>();

var servicesProvider = services.BuildServiceProvider();
var service = servicesProvider.GetRequiredService<MediPushService>();
service.Notify("google", "Hello, Google!");
}

However, AutoFac will let me combine IIndexed with Lazy or Func (IIndexed<string, Func<T>>) to avoid creating instances of services I won’t need. Doing this in MEDI would require some deeper registration gymnastics.