筆記目錄

Skip to content

在 ASP.NET 使用 Autofac 實作 DI

TLDR

  • Autofac 是 ASP.NET 生態系中強大的 DI 套件,支援多種框架整合。
  • 預設生命週期為 InstancePerDependency (Transient),可依需求調整為 InstancePerLifetimeScope (Scoped) 或 SingleInstance (Singleton)。
  • 透過 RegisterAssemblyTypes 可利用 Reflection 進行大量型別註冊,減少手動設定。
  • 循環依賴 (Circular Dependencies) 需透過 PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies) 啟用 Property Injection 解決。
  • 在 MVC 與 Web API 中,需透過 DependencyResolverDependencyResolver 設定將 Autofac 注入至 Controller。
  • Web Form 與 Web Service 因不支援 Constructor Injection,必須改用 Property Injection。
  • 建議將 AppSettings 封裝為 Options Class 並透過 Autofac 註冊,以利型別轉換與單元測試。

Autofac 基礎概念與註冊

Autofac 會在每個 Request 建立一個 Lifetime Scope。註冊型別時,使用 RegisterType<{Instance Type}>().As({Declare Type}) 來定義對應關係。

使用 Reflection 大量註冊

若要避免逐一註冊,可使用 RegisterAssemblyTypes 搜尋 Assembly 下的特定型別:

csharp
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
    .Where(x => typeof(IAppService).IsAssignableFrom(x));

指定註冊型別的對應方式

  • As():註冊給指定型別使用。
  • AsImplementedInterfaces():註冊給自身實作的 Interface 使用。
  • AsClosedTypesOf(open):註冊給開放泛型型別的封閉實例使用。
  • AsSelf():註冊給自身使用(預設值)。

TIP

若已指定 AsImplementedInterfaces() 等設定,Autofac 將不會自動加入 AsSelf(),若有需要請手動補上。

Instance Scope 管理

Autofac 提供三種主要的生命週期管理方式:

Instance Scope描述.NET Core 對應
Instance Per Dependency每次呼叫產生新 InstanceTransient
Instance Per Lifetime Scope每個 Scope 產生一個 InstanceScoped
Single Instance整個 Container 共用一個 InstanceSingleton

處理循環依賴 (Circular Dependencies)

什麼情況下會遇到這個問題:當兩個類別互相依賴(例如 Main 包含 Sub,Sub 也包含 Main)時,無法透過 Constructor Injection 完成實例化。

解決方式是改用 Property Injection 並設定允許循環依賴:

csharp
builder.RegisterType<Main>()
    .InstancePerLifetimeScope();
builder.RegisterType<Sub>()
    .InstancePerLifetimeScope()
    .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);

WARNING

  • 參與循環依賴的型別不可使用 InstancePerDependency()
  • 若無循環依賴需求,請勿傳入 PropertyWiringOptions.AllowCircularDependencies

在 MVC 與 Web API 使用 Autofac

在 MVC 或 Web API 專案中,必須在 Global.asax.cs 中註冊 Controller 並設定 DependencyResolver

MVC 範例

csharp
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
// 註冊服務
builder.RegisterType<AppService>().As<IAppService>().InstancePerLifetimeScope();

var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

模擬 ASP.NET Core 的 FromServices

若希望在 Action 參數中使用 [FromServices],可實作 IModelBinder

csharp
public class ServicesModelBinder : IModelBinder {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
        return DependencyResolver.Current.GetService(bindingContext.ModelType);
    }
}

封裝 AppSettings 為 Options 注入

什麼情況下會遇到這個問題:直接使用 WebConfigurationManager 導致型別轉換繁瑣,且因靜態類別特性不利於單元測試。

建議將設定封裝為 Options Class,並透過 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)));
    }
}

Web Form 與 Web Service 的特殊處理

什麼情況下會遇到這個問題:Web Form 與 Web Service 不支援 Constructor Injection。

必須改用 Property Injection。在 Web Form 中需於 Web.config 設定 PropertyInjectionModule;在 Web Service 中則需建立 WebServiceBase 並在建構式中手動注入:

csharp
public abstract class WebServiceBase : System.Web.Services.WebService {
    public WebServiceBase() {
        IContainerProviderAccessor cpa = (IContainerProviderAccessor)HttpContext.Current.ApplicationInstance;
        cpa.ContainerProvider.RequestLifetime.InjectProperties(this);
    }
}

異動歷程

  • 2022-11-05 初版文件建立。