依賴注入在 dotnet core 中實現與使用:4. 整合 Autofac
阿新 • • 發佈:2020-09-30
本示例使用 [.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/)