Implementing DI in ASP.NET using Autofac
Autofac
Autofac is a well-known DI package for ASP.NET. While there were other packages in the early days, many were phased out due to compatibility issues with ASP.NET Core (although ASP.NET Core has a built-in DI tool, its functionality is relatively basic, leading many to still install third-party packages for extended features).
The initial reason for choosing Autofac was its powerful type registration capabilities, detailed official documentation, and support for multiple frameworks. As it turned out, this package successfully transitioned into the ASP.NET Core era.
The usage of Autofac is shown in the example below. When used in a Web context, Autofac helps create a Lifetime Scope for each Request.
var builder = new ContainerBuilder();
// Register types
builder.RegisterType<Service>();
// Create an Autofac Container
var container = builder.Build();
// Create a Lifetime Scope
using(var scope = container.BeginLifetimeScope()) {
Service service = scope.Resolve<Service>();
}Register Type
The method used by Autofac to register types is RegisterType<{Instance Type}>().As({Declare Type}). This means that when Autofac encounters a request to obtain a Service Type, it will create and return an object of the Instance Type.
Bulk Type Registration using Reflection
Since registering every type individually is cumbersome, Autofac provides the ability to use Reflection to search for specific types within an Assembly for registration. The official website provides some examples for reference.
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(x => typeof(IAppService).IsAssignableFrom(x));Customizing Instance Creation
When a class has multiple constructors, Autofac will identify all constructors that can be instantiated (meaning all parameters can be resolved via DI) and choose the one with the most parameters. For details, refer to Darkthread - Autofac Notes 4 - Constructor Parameters and Selection. However, if you wish to define the object creation method yourself, you can use the following code:
builder.Register(c => new TypeA(c.Resolve<TypeB>()));Specifying Target Types for Registration
| Method | Description |
|---|---|
| As() | Registers the type for the specified target type. |
| AsImplementedInterfaces() | Registers the type for the interfaces it implements (excluding IDisposable). |
| AsClosedTypesOf(open) | Registers the type for closed instances assignable to the open generic type. |
| AsSelf() | Registers the type for itself. |
TIP
If no target type is set, AsSelf() is used by default. However, if a target type is specified, AsSelf() will not be added automatically. You can specify multiple targets together.
// Automatically sets AsSelf()
builder.RegisterType<Service>();
// AsImplementedInterfaces() is specified, so AsSelf() is not set automatically
builder.RegisterType<Service>()
.AsImplementedInterfaces();
// To use AsSelf() while using other specifications, you must add it manually
builder.RegisterType<Service>()
.AsImplementedInterfaces()
.AsSelf();Instance Scope
Autofac provides the following Instance Scopes:
| Instance Scope | Description | .NET Core Equivalent |
|---|---|---|
| Instance Per Dependency | A new instance is created for every call; this is the default. | Transient |
| Instance Per Lifetime Scope | Only one instance is created per scope. | Scoped |
| Single Instance | The same instance is used throughout the entire Autofac Container. | Singleton |
var builder = new ContainerBuilder();
// Register types
builder.RegisterType<Worker>()
// Declare Instance Scope
.InstancePerDependency();
//.InstancePerLifetimeScope()
// Create an Autofac Container
var container = builder.Build();
// Create a Lifetime Scope
using(var scope1 = container.BeginLifetimeScope()) {
for(var i = 0; i < 100; i++) {
// Every call
var w1 = scope1.Resolve<Worker>();
}
}
// Create a Web Request Scope
using(var scope2 = container.BeginLifetimeScope("AutofacWebRequest")) {
for(var i = 0; i < 100; i++) {
// Every call
var w1 = scope2.Resolve<Worker>();
}
}
+----------------------------------------------------+
| Autofac Container |
| |
| +------------------------------------------------+ |
| | Lifetime Scope | |
| | | |
| | +--------------------+ +--------------------+ | |
| | | Get Instance | | Get Instance | | |
| | +--------------------+ +--------------------+ | |
| +------------------------------------------------+ |
| |
| +------------------------------------------------+ |
| | Lifetime Scope | |
| | | |
| | +--------------------+ +--------------------+ | |
| | | Get Instance | | Get Instance | | |
| | +--------------------+ +--------------------+ | |
+----------------------------------------------------+TIP
InstancePerMatchingLifetimeScope({Tag}) and InstancePerRequest() are both variants of InstancePerLifetimeScope(). When creating a scope by calling container.BeginLifetimeScope({Tag}), you can set a Tag. Types declared with InstancePerMatchingLifetimeScope({Tag}) can only be created within a scope marked with that Tag. Autofac creates a scope with the tag "AutofacWebRequest" for every Web Request, and InstancePerRequest() is roughly equivalent to InstancePerMatchingLifetimeScope("AutofacWebRequest").
Official Documentation
Enabling Property Injection
- It is generally recommended to use Constructor Injection as much as possible. However, in some cases, Property Injection is necessary, such as when the framework itself does not support Constructor Injection, or when circular dependencies occur.
- A circular dependency refers to a scenario where a Main Class contains a Sub Class, and the Sub Class also contains the Main Class. The design would look like this:
class Main {
private readonly Sub sub;
public Main(Sub sub) {
this.sub = sub;
}
}
class Sub {
public Main Main { get; set; }
}
//......
builder.RegisterType<Main>()
.InstancePerLifetimeScope();
builder.RegisterType<Sub>()
.InstancePerLifetimeScope()
.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);WARNING
- Neither type registration should use
InstancePerDependency(). - If you do not need to use circular dependencies, you do not need to pass
PropertyWiringOptions.AllowCircularDependencies.
Official Documentation
Using Autofac in MVC
NuGet Packages
- Autofac
- Autofac.Mvc5
Code Example
Global.asax.cs
The following code is from the official example. RegisterControllers() is mandatory; it must be configured to allow Instance Injection into Controllers. Comments marked "OPTIONAL" should be added based on your requirements. For example, if you need to inject types like HttpContextBase, you need to register AutofacWebTypesModule.
public class MvcApplication : System.Web.HttpApplication {
protected void Application_Start() {
//...Implement MVC configuration...
// e.g., RouteConfig.RegisterRoutes(RouteTable.Routes);
// Autofac related code below
var builder = new ContainerBuilder();
// Register your MVC controllers. (MvcApplication is the name of
// the class in Global.asax.)
builder.RegisterControllers(typeof(MvcApplication).Assembly);
// OPTIONAL: Register model binders that require DI.
builder.RegisterModelBinders(typeof(MvcApplication).Assembly);
builder.RegisterModelBinderProvider();
// OPTIONAL: Register web abstractions like HttpContextBase.
builder.RegisterModule<AutofacWebTypesModule>();
// OPTIONAL: Enable property injection in view pages.
builder.RegisterSource(new ViewRegistrationSource());
// OPTIONAL: Enable property injection into action filters.
builder.RegisterFilterProvider();
builder.RegisterType<AppService>()
.As<IAppService>()
.InstancePerLifetimeScope();
// Set the dependency resolver to be Autofac.
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}HomeController
public class HomeController : Controller {
private readonly IAppService appService;
public HomeController(IAppService appService) {
this.appService = appService ?? throw new ArgumentNullException(nameof(appService));
}
//...Implement HomeController Action...
}Using DI in Views
WebViewPageBase
// View without Model
public abstract class WebViewPageBase : WebViewPage {
// Injection via builder.RegisterSource(new ViewRegistrationSource()) configuration
public IAppService AppService { get; set; }
public IAppService AppService2 => GetDependencyService<IAppService>
public TService GetDependencyService<TService>() {
return DependencyResolver.Current.GetService<TService>();
}
}
// View with Model
public abstract class WebViewPageBase<T> : WebViewPage<T> {
// Injection via builder.RegisterSource(new ViewRegistrationSource()) configuration
public IAppService AppService { get; set; }
public IAppService AppService2 => GetDependencyService<IAppService>
public TService GetDependencyService<TService>() {
return DependencyResolver.Current.GetService<TService>();
}
}Index.cshtml
Set Index.cshtml to inherit from WebViewPageBase. There are three ways to use IAppService:
- Use the
AppServiceproperty. - Use the
AppService2property. - Use
GetDependencyService<IAppService>(), which is effectively the same as usingDependencyResolver.Current.GetService<TService>()directly in the View, just simplified in the base class.
Personally, I prefer Method 2 for services used by every View, and Method 3 for services used by individual Views. If you don't use Method 1, you don't need to configure builder.RegisterSource(new ViewRegistrationSource()) in Global.asax.
Usage without Model:
@inherits DISample.MVC.WebViewPageBase
@{
IAppService appService = GetDependencyService<IAppService>();
}Usage with Model:
@inherits DISample.MVC.WebViewPageBase<ViewModel>
@{
IAppService appService = GetDependencyService<IAppService>();
}If you want to keep the original behavior of not declaring the base class when not using a Model, and using @model ViewModel when using a Model, please refer to this article to modify the \View\Web.config content.
<configuration>
<system.web.webPages.razor>
<pages pageBaseType="MyNamespace.WebViewPageBase">
</pages>
</system.web.webPages.razor>
</configuration>TIP
- Note that the "Web.config" to be modified is the one under the "View" folder, not the one in the project root.
- Replace
MyNamespacewith your actual project namespace.
Simulating ASP.NET Core's FromServicesAttribute Injection into Action Parameters
ServicesModelBinder
public class ServicesModelBinder : IModelBinder {
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
return bindingContext is null
? throw new ArgumentNullException(nameof(bindingContext))
: DependencyResolver.Current.GetService(bindingContext.ModelType);
}
}FromServicesAttribute
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class FromServicesAttribute : CustomModelBinderAttribute {
public override IModelBinder GetBinder() => new ServicesModelBinder();
}Controller Action
public ActionResult Index([FromServices] IAppService appService) {
//...Action...
return View();
}Encapsulating AppSettings for Injection
Generally, AppSettings are retrieved using WebConfigurationManager.AppSettings. However, using it directly has two drawbacks:
- AppSettings values are always
string. If you needboolor numeric types, you have to perform type conversion every time, so it is better to encapsulate this process. WebConfigurationManageris a static class. In some cases (e.g., unit testing), you might want to pass values via parameters, so some people use a Singleton for encapsulation.
The following code is based on a principle that can be adjusted as needed. The AppSetting Key must be {Options Class Name(excluding Options)}:{Constructor Parameter Name}, case-insensitive. For example, if there is an Options class named TestOptions and a constructor parameter named isTest, the AppSetting Key would be Test:IsTest.
Web.config
<appSettings>
<add key="Path:Upload" value="C:\Upload\" />
<add key="Test:IsTest" value="true" />
<add key="Test:TestName" value="Test" />
</appSettings>Global.asax.cs
public class MvcApplication : System.Web.HttpApplication {
protected void Application_Start() {
var builder = new ContainerBuilder();
builder.RegisterModule<AutofacWebTypesModule>();
// Configure Options DI
builder.RegisterModule<OptionsModule>();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}OptionsModule
The place where Option DI is actually configured.
public class OptionsModule : Module {
protected override void Load(ContainerBuilder builder) {
// Use this Register if parameters need to be determined at runtime
RegisterOptions<PathOptions>(builder);
// Use this Register for pure AppSettings configurations
RegisterOptionsInstance<TestOptions>(builder);
}
private void RegisterOptionsInstance<T>(ContainerBuilder builder)
where T : class {
builder.RegisterInstance(OptionUtils.CreateInstance<T>())
.AsImplementedInterfaces()
.AsSelf()
.SingleInstance();
}
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) => FixValue(pi.ParameterType, WebConfigurationManager.AppSettings[key])));
}
object FixValue(Type type, string value) {
return Convert.ChangeType(value, type);
}
}
}PathOptions
public class PathOptions {
private readonly HttpServerUtilityBase httpServer;
// HttpServerUtilityBase is configured for DI in AutofacWebTypesModule;
public PathOptions(HttpServerUtilityBase httpServer, string upload) {
this.httpServer = httpServer ?? throw new ArgumentNullException(nameof(httpServer));
Upload = upload ?? throw new ArgumentNullException(nameof(upload));
}
public string Upload { get; }
private string GetRealPath(string path) {
return IsVirtualDirectory(path)
? httpServer.MapPath(path)
: path;
}
private static bool IsVirtualDirectory(string path) {
return path.Length < 2 || path[1] != Path.VolumeSeparatorChar;
}
}TestOptions
public class TestOptions {
public TestOptions(bool isTest, string testName) {
IsTest = isTest;
TestName = testName ?? throw new ArgumentNullException(nameof(testName));
}
public bool IsTest { get; }
public string TestName { get; }
}Official Documentation
Using Autofac in Web API
NuGet Packages
- Autofac
- Autofac.WebApi2
Code Example
Global.asax.cs
The following code is from the official example. RegisterApiControllers() is mandatory; it must be configured to allow Instance Injection into ApiControllers. Comments marked "OPTIONAL" should be added based on your requirements.
public class WebApiApplication : HttpApplication {
protected void Application_Start() {
//...Implement Web API configuration...
//e.g., GlobalConfiguration.Configure(WebApiConfig.Register);
// Autofac related code below
ContainerBuilder builder = new ContainerBuilder();
HttpConfiguration config = GlobalConfiguration.Configuration;
// Register your Web API controllers.
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// OPTIONAL: Register the Autofac filter provider.
builder.RegisterWebApiFilterProvider(config);
// OPTIONAL: Register the Autofac model binder provider.
builder.RegisterWebApiModelBinderProvider();
builder.RegisterType<AppService>()
.As<IAppService>()
.InstancePerLifetimeScope();
// Set the dependency resolver to be Autofac.
IContainer container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
}
}ValuesController
public class ValuesController : ApiController {
private readonly IAppService appService;
public ValuesController(IAppService appService) {
this.appService = appService ?? throw new ArgumentNullException(nameof(appService));
}
//...Implement ValuesController Action...
}Official Documentation
Using Autofac in Web Forms
Since Web Forms does not support Constructor Injection, Property Injection must be used.
NuGet Packages
- Autofac
- Autofac.Web
Code Example
Web.config
<configuration>
<system.web>
<httpModules>
<!-- This section is used for IIS6 -->
<add
name="ContainerDisposal"
type="Autofac.Integration.Web.ContainerDisposalModule, Autofac.Integration.Web"/>
<add
name="PropertyInjection"
type="Autofac.Integration.Web.Forms.PropertyInjectionModule, Autofac.Integration.Web"/>
</httpModules>
</system.web>
<system.webServer>
<!-- This section is used for IIS7 -->
<modules>
<add name="ContainerDisposal" type="Autofac.Integration.Web.ContainerDisposalModule, Autofac.Integration.Web" preCondition="managedHandler" />
<add name="PropertyInjection" type="Autofac.Integration.Web.Forms.PropertyInjectionModule, Autofac.Integration.Web" preCondition="managedHandler" />
</modules>
</system.webServer>
</configuration>WARNING
The official website recommends including both configurations to support different IIS versions, but in practice, including both might cause errors.
Global.asax.cs
public class Global : HttpApplication, IContainerProviderAccessor {
private static IContainerProvider containerProvider;
public IContainerProvider ContainerProvider {
get {
return containerProvider;
}
}
protected void Application_Start(object sender, EventArgs e) {
containerProvider = new ContainerProvider(CreateContainer());
}
public static IContainer CreateContainer() {
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<AppService>()
.As<IAppService>()
.InstancePerLifetimeScope()
.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
IContainer container = builder.Build();
return container;
}
}Default.aspx.cs
public partial class _Default : Page {
public IAppService AppService { get; set; }
protected void Page_Load(object sender, EventArgs e) {
//...Implement...
}
}Official Documentation
Using Autofac in Web Service
- Web Service does not support Constructor Injection, so Property Injection must be used.
- The XML configuration in Web.config is provided for Web Forms; Web Service cannot use it to configure Property Injection, so this part must be implemented via
WebServiceBase.
NuGet Packages
- Autofac
- Autofac.Web
Code Example
Global.asax.cs
Same as Web Forms.
WebServiceBase
public abstract class WebServiceBase : System.Web.Services.WebService {
public WebServiceBase() {
IContainerProviderAccessor cpa = (IContainerProviderAccessor)HttpContext.Current.ApplicationInstance;
// Perform Property Injection for itself
cpa.ContainerProvider.RequestLifetime.InjectProperties(this);
}
}WebService
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// Uncomment the following line to allow this Web service to be called from script using ASP.NET AJAX.
// [System.Web.Script.Services.ScriptService]
public class WebService : WebServiceBase {
public IAppService AppService { get; set; } // Injection successful
//...WebService implementation...
// e.g., as follows
[WebMethod]
public DateTime GetNow() {
return AppService.GetNow();
}
}Change Log
- 2022-11-05 Initial version created.
