Alex KlausDeveloper  |  Architect  |  Leader
.NET Core project without Autofac. Is it viable?
25 March 2019

.NET Core project without Autofac

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.

  1. 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.

  2. All Autofac advanced features like:

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.

Scope mapping: Standard IoC vs Autofac

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.