開局先嘮嗑一下,許久不曾更新博客,一直在調整本身的狀態,去年是個人本命年,或許是應驗了本命年的多災多難,過得十分不順,不管是生活上仍是工做上。還好當我度過了所謂的本命年後,許多事情都在慢慢變好,我將會開始恢復更新博客,爭取恢復到之前的速度上(由於工做比較忙,因此這個過程可能須要一段時間)。html
說到屬性注入,咱們就不得不提一下 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 的一種實現方式。架構
在依賴注入時,咱們最經常使用的即是經過構造方法注入,還有另外一種方式那即是屬性注入。app
關於屬性注入,若是在網上搜索,大部份內容都是不推薦使用,或者說慎重使用的,由於屬性注入會形成類型的依賴關係隱藏,測試不友好等,我也贊成這種說法,屬性注入可使用,可是要謹慎,不能盲目使用。個人原則:在封裝框架(搭架子)時可使用,但不能大範圍使用,只有必須使用屬性注入來達到效果的地方纔會使用,用來提升使用框架時的編碼效率,來達到一些便利,脫離框架層面,編寫業務代碼時,不得使用。框架
在 ASP.NET Core 中,自帶的容器是不支持屬性注入的,可是能夠經過替換容器,如:Autofac 等來實現。今天我分享的方法不是使用替換容器,而是經過幾行代碼來實現屬性注入的效果,我稱爲「僞屬性注入」。測試
如下介紹的痛點是我在實際編碼過程當中遇到的一些,若是還有其餘的,歡迎在評論和我交流ui
我所遇到的痛點,我概括爲三條:this
1.減小經常使用的類型的重複注入代碼,使構造方法看起來更爲簡潔,提升閱讀性。編碼
2.減小或消除因構造方法注入形成子類繼承後的 base 調用鏈。設計
3.並不是是知足第一條或第二條就須要使用屬性注入來解決,只有當第1、二條發生的狀況到達必定的數量。3d
以日誌 ILogger<T>
爲例,咱們在 Controller 或者 應用服務層(Application Service)等編寫業務的地方可能會經常使用,那麼咱們可能會在大部分的 Controller 或者 Application Service 的構造方法裏寫一句注入,例:
這裏只是以日誌來舉例,咱們還能遇到和日誌這種相同的類型,每一個 Controller 等都要注入一堆這種共同的類型,代碼編寫起來也比較麻煩,若是多了之後還影響代碼閱讀。
有何解決辦法,那就是定義一個基類,而後經過屬性提供給子類,以 Controller 爲例:
在上面的Controller基類注入 ILogger,而後設置了 Logger 屬性,這樣子類就可使用 Logger 屬性來使用日誌。
這樣作每次都要調用 base 將依賴對象傳遞給基類,若是繼承關係有多層,將會形成更大的影響。
注意:本文演示只以日誌來舉例,若是隻有一個ILogger我以爲還能夠忍受,實際狀況中並不是只有一個,好比本地化等等。博主不提倡有上面演示狀況的就用屬性注入,當到達必定數量才使用,好比在 Controller 或者應用服務這種數量多的對象以及當這些對象須要的共同的注入類型達到必定數量。
依託於 ASP.NET Core 自帶的容器,在 Resolve Service 時,爲須要「屬性注入」的屬性進行賦值,可使用 自帶容器提供的 ImplementationFactory
來實現。
Controller 的實現較爲特殊,Controller 默認是不會經過自帶容器來 Resolve&Activate 的,是經過MVC自身管理的,可是微軟提供了這樣的方法:
services.AddControllers().AddControllersAsServices();
能夠經過調用 AddControllersAsServices()
方法來讓 Controller 使用自帶容器,其主要源代碼以下
根據第四小節的思想,咱們須要 Controller Resolve 時,來對屬性進行賦值,那麼咱們須要改造 Controller 激活器。
能夠看到咱們改造的代碼也就幾行。
services.AddControllers().AddControllersAsServices(); services.Replace(ServiceDescriptor.Transient<IControllerActivator, XcServiceBasedControllerActivator>()); //替換默認 Controller 激活器
測試正常,如需其餘屬性的「屬性注入」,參考日誌這樣作就好了。
只是以 Application Service 來做爲講解,同理可觸類旁通到其餘地方。Application Service 屬於領域驅動分層架構中的一層,如不瞭解,可自行查找資料。
public interface IAppService { ILogger Logger { get; set; } } public class AppService:IAppService { public ILogger Logger { get; set; } }
public interface IUserAppService:IAppService { void Create(); } public class UserAppService : AppService,IUserAppService { public void Create() { Logger.LogInformation("來自 Application Service 的日誌"); } }
public static class ServiceExtensions { public static IServiceCollection AddApplicationService<TService, TImpl>(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<ILoggerFactory>().CreateLogger(implType); } return implInstance; }); return services; }
其實到本文寫完,我都在想,要不要封裝一個組件,發佈到 Nuget 來方便的使用文中我所描述的「僞屬性注入」,最後反覆想了想,仍是以爲不作。若是要使用徹底的屬性注入能夠替換使用第三方容器,本文所述旨在不想引入第三方容器,且想在部分地方來達到屬性注入的效果,由於屬性注入這個東西也不推薦大範圍使用。
本文來源於我在工做中的一些靈感總結,我在看 ControllerActivator
源碼時的突發奇想,最近工做雖然忙,可是知識確實積攢了很多,在後面與你們一一分享。