ASP.NET Core 奇淫技巧之偽屬性注入
阿新 • • 發佈:2020-04-01
## 一.前言
開局先嘮嗑一下,許久未曾更新部落格,一直在調整自己的狀態,去年是我的本命年,或許是應驗了本命年的多災多難,過得十分不順,不論是生活上還是工作上。還好當我度過了所謂的本命年後,許多事情都在慢慢變好,我將會開始恢復更新部落格,爭取恢復到以前的速度上(因為工作比較忙,所以這個過程可能需要一段時間)。
## 二.關於屬性注入
說到屬性注入,我們就不得不提一下 DI(Dependency Injection),即依賴注入,用過 ASP.NET Core 的同學相信對這個詞不會陌生。ASP.NET Core 自帶了一個IOC容器,且程式執行也是基於這個容器建立起來的,在 Startup 裡的 `ConfigureServices` 方法就是向容器註冊型別。最直白的講,我們在 ASP.NET Core 中,想使用某個型別的時候可以不用自己去 new,可以由容器通過構造方法來注入具體的實現型別,而我們一般在構造方法上定義的依賴型別都是介面,而不是去依賴具體的實現,這裡就體現了 SOLID 原則中的依賴倒置原則(DIP)。這也是IOC(Inversion of Control),即控制反轉,不直接依賴具體實現,將依賴交給容器去控制。上述幾者是具有一定的關聯關係的,DIP 是一種軟體設計原則,IOC 是 DIP 的具體實現方式,DI 是 IOC 的一種實現方式。
在依賴注入時,我們最常用的便是通過構造方法注入,還有另一種方式那便是**屬性注入**。
關於屬性注入,如果在網上搜索,大部分內容都是不推薦使用,或者說慎重使用的,因為屬性注入會造成型別的依賴關係隱藏,測試不友好等,我也同意這種說法,屬性注入可以使用,但是要謹慎,不能盲目使用。我的原則:在封裝框架(搭架子)時可以使用,但不能大範圍使用,只有必須使用屬性注入來達到效果的地方才會使用,用來提高使用框架時的編碼效率,來達到一些便利,脫離框架層面,編寫業務程式碼時,不得使用。
在 ASP.NET Core 中,自帶的容器是不支援屬性注入的,但是可以通過替換容器,如:Autofac 等來實現。今天我分享的方法不是使用替換容器,而是通過**幾行程式碼**來實現屬性注入的效果,我稱為“**偽屬性注入**”。
## 三.屬性注入解決的痛點
> 以下介紹的痛點是我在實際編碼過程中遇到的一些,如果還有其他的,歡迎在評論和我交流
我所遇到的痛點,我歸納為三條:
1.減少**常用**的型別的重複注入程式碼,使構造方法看起來更為簡潔,提高閱讀性。
2.減少或消除因構造方法注入造成子類繼承後的 base 呼叫鏈。
3.**並非是滿足第一條或第二條就需要使用屬性注入來解決,只有當第一、二條發生的情況到達一定的數量。**
### 第一條:
以日誌 `ILogger` 為例,我們在 Controller 或者 應用服務層(Application Service)等編寫業務的地方可能會常用,那麼我們可能會在大部分的 Controller 或者 Application Service 的構造方法裡寫一句注入,例:
![1585671450000](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013227435-457592325.png)
這裡只是以日誌來舉例,我們還能遇到和日誌這種相同的型別,每個 Controller 等都要注入一堆這種共同的型別,程式碼編寫起來也比較麻煩,如果多了以後還影響程式碼閱讀。
有何解決辦法,那就是定義一個基類,然後通過屬性提供給子類,以 Controller 為例:
![1585671766565](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013226915-894150528.png)
### 第二條:
在上面的Controller基類注入 ILogger,然後設定了 Logger 屬性,這樣子類就可以使用 Logger 屬性來使用日誌。
這樣做每次都要呼叫 base 將依賴物件傳遞給基類,如果繼承關係有多層,將會造成更大的影響。
![1585671919190](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013226386-1626101375.png)
> 注意:本文演示只以日誌來舉例,如果只有一個ILogger我覺得還可以忍受,實際情況中並非只有一個,比如本地化等等。博主不提倡有上面演示情況的就用屬性注入,當到達一定數量才使用,比如在 Controller 或者應用服務這種**數量多的物件**以及當這些物件需要的共同的注入型別達到一定數量。
## 四. 偽屬性注入核心思想
依託於 ASP.NET Core 自帶的容器,在 Resolve Service 時,為需要“屬性注入”的屬性進行賦值,可以使用 自帶容器提供的 `ImplementationFactory` 來實現。
## 五. 為 Controller 實現偽屬性注入
Controller 的實現較為特殊,Controller 預設是不會通過自帶容器來 Resolve&Activate 的,是通過MVC自身管理的,但是微軟提供了這樣的方法:
````csharp
services.AddControllers().AddControllersAsServices();
````
可以通過呼叫 `AddControllersAsServices() `方法來讓 Controller 使用自帶容器,其主要原始碼如下
![1585673407205](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013225866-108548464.png)
根據第四小節的思想,我們需要 Controller Resolve 時,來對屬性進行賦值,那麼我們需要改造 Controller 啟用器。
1. 定義 Controller 基類
![1585673817076](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013225244-2133931778.png)
2. Controller 繼承基類
![1585673605165](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013224701-176205022.png)
3. 改造 Controller 啟用器
![1585673898053](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013223429-462289742.png)
可以看到我們改造的程式碼也就幾行。
4. 替換預設 Controller 啟用器
````csharp
services.AddControllers().AddControllersAsServices();
services.Replace(ServiceDescriptor.Transient()); //替換預設 Controller 啟用器
````
5. 執行測試
![1585674115498](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013220461-1730139495.png)
測試正常,如需其他屬性的“屬性注入”,參考日誌這樣做就行了。
## 六. 為 Application Service 實現偽屬性注入
> 只是以 Application Service 來作為講解,同理可舉一反三到其他地方。Application Service 屬於領域驅動分層架構中的一層,如不瞭解,可自行查詢資料。
1. 定義應用服務**基類介面**
````csharp
public interface IAppService
{
ILogger Logger { get; set; }
}
public class AppService:IAppService
{
public ILogger Logger { get; set; }
}
````
2. 定義具體服務,以 User 服務為例
````csharp
public interface IUserAppService:IAppService
{
void Create();
}
public class UserAppService : AppService,IUserAppService
{
public void Create()
{
Logger.LogInformation("來自 Application Service 的日誌");
}
}
````
3. 定義特殊的註冊服務的方法,以便實現 Resolve 為 Logger 賦值
````csharp
public static class ServiceExtensions
{
public static IServiceCollection AddApplicationService(this IServiceCollection services) where TService:IAppService where TImpl:AppService
{
services.AddApplicationService(typeof(TService), typeof(TImpl));
return services;
}
// 可以反射程式集呼叫此方法實現批量自動註冊應用服務
public static IServiceCollection AddApplicationService(this IServiceCollection services, Type serviceType,Type implType)
{
services.AddTransient(serviceType, sp =>
{
//獲取服務實現的例項
var implInstance = ActivatorUtilities.CreateInstance(sp, implType); ;
if (implInstance is AppService obj)
{
//為 Logger 賦值
obj.Logger= sp.GetRequiredService().CreateLogger(implType);
}
return implInstance;
});
return services;
}
````
4. 註冊測試服務
![1585675259627](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013219798-1540270619.png)
5. Controller 注入測試服務
![1585675328416](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013219194-203604995.png)
6. 執行測試
![1585675427275](https://img2020.cnblogs.com/blog/668104/202004/668104-20200401013218333-1240655168.png)
## 七.結束
其實到本文寫完,我都在想,要不要封裝一個元件,釋出到 Nuget 來方便的使用文中我所描述的“偽屬性注入”,最後反覆想了想,還是覺得不做。如果要使用完全的屬性注入可以替換使用第三方容器,本文所述旨在不想引入第三方容器,且想在**部分地方**來達到屬性注入的效果,因為屬性注入這個東西也不推薦大範圍使用。
本文來源於我在工作中的一些靈感總結,我在看` ControllerActivator` 原始碼時的突發奇想,最近工作雖然忙,但是知識確實積攢了不少,在後面與大家一一分享。
姊妹篇:[ASP.NET Core 奇淫技巧之動態WebApi](https://www.cnblogs.com/stulzq/p/11007770.html)