Implementing DI in ASP.NET using Autofac
TLDR
- Autofac is a powerful DI package in the ASP.NET ecosystem that supports integration with various frameworks.
- The default lifetime is
InstancePerDependency(Transient), which can be adjusted toInstancePerLifetimeScope(Scoped) orSingleInstance(Singleton) as needed. RegisterAssemblyTypesallows for bulk type registration using Reflection, reducing manual configuration.- Circular Dependencies must be resolved by enabling Property Injection via
PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies). - In MVC and Web API, Autofac must be injected into Controllers via
DependencyResolverorDependencyResolverconfiguration. - Web Forms and Web Services do not support Constructor Injection and must use Property Injection instead.
- It is recommended to encapsulate AppSettings into Options Classes and register them via Autofac to facilitate type conversion and unit testing.
Autofac Basics and Registration
Autofac creates a Lifetime Scope for every Request. When registering types, use RegisterType<{Instance Type}>().As({Declare Type}) to define the mapping.
Bulk Registration using Reflection
To avoid registering types one by one, you can use RegisterAssemblyTypes to search for specific types within an Assembly:
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(x => typeof(IAppService).IsAssignableFrom(x));Specifying Registration Mappings
As(): Registers for a specific type.AsImplementedInterfaces(): Registers for the interfaces implemented by the type itself.AsClosedTypesOf(open): Registers for closed instances of an open generic type.AsSelf(): Registers for the type itself (default).
TIP
If settings like AsImplementedInterfaces() have already been specified, Autofac will not automatically add AsSelf(). If needed, please add it manually.
Instance Scope Management
Autofac provides three primary ways to manage lifetimes:
| Instance Scope | Description | .NET Core Equivalent |
|---|---|---|
| Instance Per Dependency | Creates a new instance for every call | Transient |
| Instance Per Lifetime Scope | Creates one instance per scope | Scoped |
| Single Instance | Shares one instance across the entire container | Singleton |
Handling Circular Dependencies
When does this issue occur?: When two classes depend on each other (e.g., Main contains Sub, and Sub contains Main), instantiation cannot be completed via Constructor Injection.
The solution is to switch to Property Injection and enable circular dependency support:
builder.RegisterType<Main>()
.InstancePerLifetimeScope();
builder.RegisterType<Sub>()
.InstancePerLifetimeScope()
.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);WARNING
- Types involved in circular dependencies cannot use
InstancePerDependency(). - If there is no requirement for circular dependencies, do not pass
PropertyWiringOptions.AllowCircularDependencies.
Using Autofac in MVC and Web API
In MVC or Web API projects, you must register Controllers and configure the DependencyResolver in Global.asax.cs.
MVC Example
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
// Register services
builder.RegisterType<AppService>().As<IAppService>().InstancePerLifetimeScope();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));Simulating ASP.NET Core's FromServices
If you wish to use [FromServices] in Action parameters, you can implement IModelBinder:
public class ServicesModelBinder : IModelBinder {
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
return DependencyResolver.Current.GetService(bindingContext.ModelType);
}
}Encapsulating AppSettings as Options for Injection
When does this issue occur?: Using WebConfigurationManager directly leads to cumbersome type conversion, and its static nature makes unit testing difficult.
It is recommended to encapsulate settings into an Options Class and register them via a Module:
private void RegisterOptions<T>(ContainerBuilder builder) where T : class {
string optionsName = typeof(T).Name.Replace("Options", "");
var registrationBuilder = builder.RegisterType<T>().AsSelf().InstancePerLifetimeScope();
foreach (string key in WebConfigurationManager.AppSettings.AllKeys.Where(x => x.StartsWith(optionsName))) {
registrationBuilder.WithParameter(new ResolvedParameter(
(pi, ctx) => pi.Name.Equals(Regex.Replace(key, $@"^{optionsName}:", "", RegexOptions.IgnoreCase), StringComparison.OrdinalIgnoreCase),
(pi, ctx) => Convert.ChangeType(WebConfigurationManager.AppSettings[key], pi.ParameterType)));
}
}Special Handling for Web Forms and Web Services
When does this issue occur?: Web Forms and Web Services do not support Constructor Injection.
You must switch to Property Injection. In Web Forms, you need to configure PropertyInjectionModule in Web.config; in Web Services, you need to create a WebServiceBase and manually inject properties in the constructor:
public abstract class WebServiceBase : System.Web.Services.WebService {
public WebServiceBase() {
IContainerProviderAccessor cpa = (IContainerProviderAccessor)HttpContext.Current.ApplicationInstance;
cpa.ContainerProvider.RequestLifetime.InjectProperties(this);
}
}Change Log
- 2022-11-05 Initial document creation.
