1. 程式人生 > >依賴注入在 dotnet core 中實現與使用:4. 整合 Autofac

依賴注入在 dotnet core 中實現與使用:4. 整合 Autofac

本示例使用 [.net core 5 rc-1](https://dotnet.microsoft.com/download/dotnet/5.0) 實現。 ### 1. 新增 Nuget 包引用 使用 Autofac 當然要新增 Autofac 的 Nuget 包,主要涉及到兩個: * Autofac.Extensions.DependencyInjection 核心支援包 * Autofac.Extras.DynamicProxy2 AOP 動態代理支援 如果不需要動態代理的話,只需要新增第一個即可。 ```bash dotnet add package Autofac.Extensions.DependencyInjection ``` ### 2. 配置 Autofac 首先需要需要配置 Autofac 的容器工廠。 由於需要使用 Autofac 的容器,所以在構建應用程式的時候,需要使用 Autofac 的服務工廠。主程式 Program 中的 CreateHostBuilder() 方法需要增加一行,修改之後如下所示: ```csharp public static IHostBuilder CreateHostBuilder (string[] args) => Host.CreateDefaultBuilder (args) .UseServiceProviderFactory (new AutofacServiceProviderFactory ()) .ConfigureWebHostDefaults (webBuilder => { webBuilder.UseStartup (); }); } ``` 然後,需要在 Startup() 中配置服務註冊。 Autofac 的服務工廠會在呼叫 ConfigureServices() 之後,自動呼叫名為 ConfigureContainer() 的方法,一般情況下,我們會在這個方法裡面使用 Autofac 來註冊服務。 在 Startup 檔案中,新增如下的 ConfigureContainer() 方法。ContainerBuilder 是定義在名稱空間 Autofac 中的,注意新增對該名稱空間的引用。 ```csharp using Autofac; public void ConfigureContainer (ContainerBuilder builder) { ...... } ``` Autofac 提供了各種註冊服務的方法,不是微軟的 Addxxx() 方式,而是 Registerxxx() 方式。 例如,如果我們已經定義了一個 IDbService 介面,而它的實現型別是 DbService。那麼,註冊服務的形式如下所示: ```csharp // register type, and enable interceptor injection builder.RegisterType ().As () .InstancePerLifetimeScope (); ``` DbService 是註冊在容器中的實現型別,而 As\ 是在容器中註冊的型別。注入的時候需要使用這個介面型別。InstancePerLifetimeScope() 則是說明它的生命週期是 Scope 型別的。 可以看到,在 Autofac 中,使用鏈式呼叫的方式來完成服務註冊。 ### 3. 使用 Autofac Module 進行註冊 Autofac 提供了一個名為 Module 的概念,它支援將一組相關的服務註冊過程進行打包,以簡化配置和部署。 Autofac 提供了名為 Autofac.IModule 介面,以及一個它的抽象實現型別 Autofac.Module。它的核心是 Load() 方法,用來完成服務的註冊。我們可以過載它以實現自定義的服務註冊,該方法的簽名如下: ```csharp protected virtual void Load( ContainerBuilder builder ) ``` 可以看到該方法提供同樣的 ContainerBuilder 引數來提供服務註冊的支援。 這樣的話,前面的服務註冊可以轉移到一個 Autofac 的 Module 中來。 我們可以定義一個服務註冊類,如下所示: ```csharp using Autofac; using Microsoft.AspNetCore.Mvc; public class ServiceAutofacModule : Autofac.Module { protected override void Load (ContainerBuilder builder) { // register type, and enable interceptor injection builder.RegisterType ().As () .InstancePerLifetimeScope (); } } ``` 然後,將 Startup() 中的 ConfigureContainer() 調整為如下形式,使用 Module 的方式完成服務註冊。 ```csharp public void ConfigureContainer (ContainerBuilder builder) { // use autofac module builder.RegisterModule(); } ``` Module 的使用詳見:https://autofaccn.readthedocs.io/en/latest/configuration/modules.html ### 4. 常見的註冊方式 #### 1. 按照型別進行註冊 ```csharp // register type, and enable interceptor injection builder.RegisterType ().As () .InstancePerLifetimeScope (); ``` #### 2. 按已經引用的程式集註冊 ```csharp var assembly = assembly.Load ("Domain.Services"); builder.registerAssemblyType (assembly) .AsImplementedInterfaces () .InstancePerLifetimeScope (); ``` #### 3. 註冊程式集中的某些服務 下面的程式碼中,先取得了 ControllerBase 的型別,然後在當前程式集中查詢所有派生自 ControllerBase 的 Api 控制器 ```csharp builder.RegisterAssemblyTypes(typeof(Program).Assembly) .Where(t => t.Name.EndsWith("Service")) .AsImplementedInterfaces() .InstancePerLifetimeScope(); ``` ### 5. 使用屬性注入 Autofac 除了支援建構函式注入,還支援屬性注入,屬性注入會在建構函式注入之後進行。 必須要注意的是,必須在使用屬性注入的服務上進行宣告, 例如,如果 DbService 需要支援屬性注入,那麼需要在註冊該服務的時候進行宣告。 ```csharp builder.RegisterType ().As () .PropertiesAutowired() .InstancePerLifetimeScope (); ``` #### ASP.NET Core 中,對控制器進行屬性注入的特殊處理 預設情況下,ASP.NET Core 對於控制器並不是從容器中建立的,所以如果你檢查容器中的註冊,是看不到控制器的註冊的。 為了支援屬性注入,需要讓 ASP.NET Core 將控制器也註冊到容器中。這可以在 AddControllers() 方法之後,呼叫 AddControllersAsServices() 來實現。 ```csharp public void ConfigureServices (IServiceCollection services) { services.AddControllers() .AddControllersAsServices(); } ``` 然後,我們需要對控制器新增支援屬性注入的宣告。 既可以針對單個的控制器類 ```csharp // make property autowire at one controller builder.RegisterType() .PropertiesAutowired(); ``` 也可以針對所有的控制器。 ```csharp // make property autowire at all api controller var controllerBaseType = typeof (ControllerBase); builder.RegisterAssemblyTypes (typeof (Program).Assembly) .Where (t => controllerBaseType.IsAssignableFrom (t) && t != controllerBaseType) .PropertiesAutowired (); ``` ### 6. 使用 AOP 動態代理 使用 AOP 需要如下的 4 個步驟。 #### 1. 定義攔截器 攔截器的介面 IInterceptor 定義在名稱空間 Castle.DynamicProxy 中,需要注意的是,它需要新增對 NuGet 包 Autofac.Extras.DynamicProxy 的引用。 ```bash dotnet add package Autofac.Extras.DynamicProxy ``` 實現 IInterceptor 介面。 ```csharp using Castle.DynamicProxy; using System; public class DbServiceInterceptor:IInterceptor { public virtual void Intercept(IInvocation invocation) { Console.WriteLine($"{DateTime.Now}: Before method execting. "); invocation.Proceed(); Console.WriteLine($"{DateTime.Now}: After method exected."); } } ``` #### 2. 註冊攔截器 攔截器也同樣需要註冊到容器中。 ```csharp // register interceptor builder.RegisterType (); ``` #### 3. 啟用攔截器 需要支援攔截器的服務需要啟用攔截器,然後才能使用攔截器。 ```csharp // register type, and enable interceptor injection builder.RegisterType ().As () .EnableInterfaceInterceptors () .InstancePerLifetimeScope (); ``` 可以使用 EnableInterfaceInterceptors() 或者 EnableClassInterceptors() 擴充套件方法來啟用攔截器。 EnableInterfaceInterceptors() 建立介面代理來執行攔截,而 EnableClassInterceptors() 則建立目標元件的子類來執行攔截。 #### 4. 使用攔截器 第一種方式是在使用攔截器的服務上,通過特性來宣告使用的攔截器。 ```csharp using Autofac.Extras.DynamicProxy; using Castle.DynamicProxy; [Intercept (typeof (DbServiceInterceptor))] public class DbService : IDbService { public string Say () { return "Hello"; } } ``` 當使用特性來關聯攔截器的時候,不需要在註冊服務的時候指定攔截器。你只需要啟用,實際的攔截器將被自動發現。 第二種方式是在註冊服務的時候指定,使用 InterceptedBy() 擴充套件方法。 ```csharp builder.RegisterType() .EnableClassInterceptors() .InterceptedBy(typeof(CallLogger)); ``` 注意: * 使用公共介面 * 類攔截要求被攔截的方法是虛方法,因為使用了子類代理技術。 * 通過表示式建立的服務,或者使用例項註冊的服務,不能使用子類方式代理,此時,要使用介面代理。 * 要使用介面代理,服務必須僅僅通過介面提供服務,為了最佳的效能,所有此類服務介面必須是註冊的一部分,例如使用 .As 子句。 * 如果通過 EnableClassInterceptors() 使用了類攔截,則避免使用建構函式選擇器 UsingConstructor()。在使用類攔截的時候,會為代理類生成新的建構函式以獲取你希望使用的攔截器。如果你使用了 UsingConstructor(),就會跳過此邏輯。導致攔截器不能被使用。 已知問題: * 同步方法攔截。Castle 攔截器僅僅支援同步方法攔截。不支援顯式的 async/await 方法。但是,async/await 是 Task 的語法糖,你可以在攔截器中使用 Task 和 ContinueWith() 之類的方法。 [This issue](https://github.com/castleproject/Core/issues/107) 展示了用法。另外,[這些助手類](https://github.com/JSkimming/Castle.Core.AsyncInterceptor) 也使得 async 工作更容易一點。 * Castle.Core 版本問題。 ### 參考資料 * [Autofac 官網](https://autofac.org/) * [Autofac Document](https://autofaccn.readthedocs.io/en/latest/index.html) * [Autofac at GitHub](https://github.com/autofac/Autofac) * [Sample Project](https://github.com/haoguanjun/dotnetcore_autofac_sample) * [Aspect Oriented Programming (AOP) in .NET Core and C# using AutoFac and DynamicProxy](https://nearsoft.com/blog/aspect-oriented-programming-aop-in-net-core-and-c-using-autofac-and-dynamicproxy/)