Perhaps, Autofac is the most popular DI/IoC container for ASP.NET projects. But since .NET Core has a pretty decent DI/IoC container out-of-the-box, why would one still need Autofac? For the sake of simplicity and maintainability I attempted to remove Autofac dependencies from a mid-sized project and use the standard container only. Here is a story on how did it go.
Concerns of using Autofac in ASP.NET Core project (minor ones)
Power and flexibility of Autofac comes with a price – complexity. Often, new developers coming on the project get puzzled by the ambiguity in registering new dependencies.
What scope should I use?
Autofac provides 7 lifetime scopes, which require a bit of a reading before using.
On the other side, the standard DI/IoC container provides just 3 scopes: Transient
, Scoped
and Singleton
. Less options – less chance to use a wrong one for new devs.
Most controversy happens around two Autofac scopes: PerLifetimeScope
and InstancePerRequest
. The official FAQ still warns against using PerLifetimeScope
and advises utilising InstancePerRequest
to reduce memory footprint. Though, a more recent article in the official docs tells that there is no difference between the two, but recommends using PerLifetimeScope
.
Use standard or Autofac dependency registration?
It’s unusual to see an ASP.NET Core project without usage of the standard DI/IoC container, as many code samples and the out-of-the-box boilerplate use it. At the same time, some other dependencies may be registered via Autofac. It introduces ambiguity on which container to use for registration of a new dependency.
Things you may miss after leaving Autofac
The standard DI/IoC container is very lightweight. Here what you may miss onced limit yourself to the standard container only.
-
Beloved helper methods.
IMHO, the most common/loved Autofac feature is Scanning for Modules to register dependencies. Many devs prefer bundling up logically-related dependency in Autofac Modules and just provide assemblies to register them all.
-
All Autofac advanced features like:
- Metadata registrations and Named/Keyed services to build collections of dependencies that are selectable by some sort of key (e.g. for implementing Strategy pattern).
- Aggregate Services for treating a set of dependencies as one dependency.
- Multitenant support for tweaking the DI depending on the application tenant.
Moving away from Autofac
Still here and ready to go?
Step 1. Mapping scopes and registrations
If you don’t use some rare Autofac scopes, then switching to the lifetime scopes of the standard container for dependency registration is straightforward, like shown on the schema below.
Simple registration would change from the Autofac way
var builder = new ContainerBuilder();
builder.RegisterType<Foo>().AsImplementedInterfaces().SingleInstance();
builder.RegisterType<Boo>().AsImplementedInterfaces().InstancePerLifetimeScope();
to
// IServiceCollection services
services.AddSingleton<IFoo, Foo>();
services.AddScoped<IBoo, Boo>();
The first catch – lack of AsImplementedInterfaces()
, a helper method to register the type as providing all of its public interfaces. You’d need to add helper methods manually by bringing in a small NuGet library (e.g. NetCore.AutoRegisterDi) or writing it. That particular one is trivial:
private static void RegisterAsImplementedInterfaces<TService>(this IServiceCollection services, ServiceLifetime lifetime)
{
var interfaces = typeof(TService).GetTypeInfo().ImplementedInterfaces
.Where(i => i != typeof(IDisposable) && (i.IsPublic));
foreach (Type interfaceType in interfaces)
services.Add(new ServiceDescriptor(interfaceType, typeof(TService), lifetime));
}
// use it like:
services.RegisterAsImplementedInterfaces<Foo>(ServiceLifetime.Scoped);
Step 2. Helper methods
Autofac supplies many useful generic helper methods, not dependent on its classes/interfaces (e.g. IsClosedTypeOf()
). You can check out the code of their TypeExtensions.cs on GitHub. Again, if you need them, write/copy the required methods.
In order to simulate Autofac’s Scanning for Modules feature you may need your own implementation of Module class:
public abstract class Module
{
public void Configure(IServiceCollection services)
{
Load(services);
}
protected abstract void Load(IServiceCollection services);
}
with derived classes like
internal class MyModule : Module
{
protected override void Load(IServiceCollection services)
{
services.RegisterAsImplementedInterfaces<MyClass>();
// add other registrations here...
}
}
and a small helper method to scan for implementations of your Module
:
public static void RegisterModules(this IServiceCollection services, Assembly assembly)
{
foreach (Type tp in assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Module))))
{
if (Activator.CreateInstance(tp) is Module module)
module.Configure(services);
}
}
Conclusion
Sticking to the standard .NET Core container may eliminate some complexity and barriers for junior devs, but may require a bit of extra coding. It all can be justified on smaller projects. When on big projects having rich functionality of Autofac brings much more benefits and Autofac may become irreplaceable.
For non-greenfield projects, switching the DI/IoC container is hard to justify, though it all depends on how heavily the team has invested in the container.
Any thoughts? Write a comment below or on Twitter.