On this page

Skip to content

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 to InstancePerLifetimeScope (Scoped) or SingleInstance (Singleton) as needed.
  • RegisterAssemblyTypes allows 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 DependencyResolver or DependencyResolver configuration.
  • 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:

csharp
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 ScopeDescription.NET Core Equivalent
Instance Per DependencyCreates a new instance for every callTransient
Instance Per Lifetime ScopeCreates one instance per scopeScoped
Single InstanceShares one instance across the entire containerSingleton

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:

csharp
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

csharp
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:

csharp
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:

csharp
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:

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