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.
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] publicvoidAutoFac_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.
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] publicvoidAutoFac_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] publicvoidMEDI_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.
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] publicvoidAutoFac_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] publicvoidMEDI_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.
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] publicvoidAutoFac_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] publicvoidMEDI_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.
[Fact] publicvoidAutoFac_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.
[Fact] publicvoidAutoFac_NamedScope() { // imagine all this is in some global test setup area var builder = new ContainerBuilder(); builder.RegisterType<UnitOfWork>() .AsImplementedInterfaces() .InstancePerLifetimeScope();
// 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.
publicvoidMaybeUseService() { if (Random.Shared.NextDouble() > 0.5) { var expensiveService = _expensiveServiceFactory(); // do something with expensive service } } }
[Fact] publicvoidAutoFac_AutomaticallySupportsFuncT() { var builder = new ContainerBuilder(); builder.RegisterType<ExpensiveService>().As<IExpensiveService>(); builder.RegisterType<MightNotNeedExpensiveService>(); var container = builder.Build();
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();
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.
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] publicvoidAutoFac_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] publicvoidMEDI_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] publicvoidAutoFac_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");
[Fact] publicvoidAutoFac_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:
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] publicvoidMEDI_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.
[Fact] publicvoidAutoFac_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.
[Fact] publicvoidMEDI_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.