對於 ASP.NET Core 的依賴注入、控制反轉以及 Autofac 等一直沒有搞明白,但這篇文章讓我從根本上了解了尤其是依賴注入的概念以及在 ASP.NET Core 中的應用,特推薦給需要的你。
一、什麼是依賴注入(Denpendency Injection)
這也是個老生常談的問題,到底依賴注入是什麼? 爲什麼要用它? 初學者特別容易對控制反轉IOC(Iversion of Control),DI等概念搞暈。
1.1依賴
當一個類需要另一個類協作來完成工作的時候就產生了依賴。比如我們在AccountController這個控制器需要完成和用戶相關的註冊、登錄等事情。其中的登錄由EF結合Idnetity來完成,所以我們封裝了一個EFLoginService。這裏AccountController就有一個ILoginService的依賴。
這裏有一個設計原則:依賴於抽象,而不是具體的實現。所以我們給EFLoginService定義了一個接口,抽象了LoginService的行爲。
1.2 什麼是注入
注入體現的是一個IOC(控制反轉的思想)。在反轉之前 ,我們先看看正轉。
AccountController自己來實例化需要的依賴。
1 2 3 4 5 |
|
大師說,這樣不好。你不應該自己創建它,而是應該由你的調用者給你。於是你通過構造函數讓外界把這兩個依賴傳給你。
1 2 3 4 5 |
|
把依賴的創建丟給其它人,自己只負責使用,其它人丟給你依賴的這個過程理解爲注入。
1.3 爲什麼要反轉?
爲了在業務變化的時候盡少改動代碼可能造成的問題。
比如我們現在要把從EF中去驗證登錄改爲從Redis去讀,於是我們加了一個 RedisLoginService。這個時候我們只需要在原來注入的地方改一下就可以了。
1 2 3 4 5 |
|
// 用Redis來替換原來的EF登錄
var controller = new AccountController(new RedisLoginService()); controller.Login(userName, password);
1.4 何爲容器
上面我們在使用AccountController的時候,我們自己通過代碼創建了一個ILoggingServce的實例。想象一下,一個系統中如果有100個這樣的地方,我們是不是要在100個地方做這樣的事情? 控制是反轉了,依賴的創建也移交到了外部。現在的問題是依賴太多,我們需要一個地方統一管理系統中所有的依賴,因此容器誕生了。
容器負責兩件事情:
- 綁定服務與實例之間的關係
- 獲取實例,並對實例進行管理(創建與銷燬)
二、.NET Core DI
2.1 實例的註冊
前面講清楚DI和Ioc的關鍵概念之後,我們先來看看在控制檯中對 .NET Core DI 的應用。在 .NET Core 中 DI 的核心分爲兩個組件:IServiceCollection 和 IServiceProvider。
- IServiceCollection 負責註冊
- IServiceProvider 負責提供實例
通過默認的 ServiceCollection(在Microsoft.Extensions.DependencyInjection命名空間下)有三個方法:
1 2 3 4 |
|
這三個方法都是將我們的實例註冊進去,只不過實例的生命週期不一樣。什麼是生命週期我們下一節接着講。
ServiceCollection的默認實現是提供一個ServiceDescriptor的List
1 2 3 |
|
我們上面的AddTransient、AddSignletone和Scoped方法是IServiceCollection的擴展方法, 都是往這個List裏面添加ServiceDescriptor。
1 2 3 4 5 6 7 8 9 10 11 |
|
2.2 實例的生命週期之單例
我們上面看到了,.NET Core DI 爲我們提供的實例生命周其包括三種:
- Transient: 每一次 GetService 都會創建一個新的實例
- Scoped: 在同一個 Scope 內只初始化一個實例 ,可以理解爲( 每一個 request 級別只創建一個實例,同一個 http request會在一個 scope 內)
- Singleton :整個應用程序生命週期以內只創建一個實例
對應了Microsoft.Extensions.DependencyInjection.ServiceLifetime的三個枚舉值
1 2 3 4 5 6 |
|
爲了大家能夠更好的理解這個生命週期的概念我們做一個測試:
定義一個最基本的 IOperation 裏面有一個 OperationId 的屬性,IOperationSingleton也是一樣,只不過是另外一個接口。
1 2 3 4 5 6 7 |
|
我們的 Operation 實現很簡單,可以在構造函數中傳入一個Guid進行賦值,如果沒有的話則自已New一個 Guid。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
在程序內我們可以多次調用ServiceProvider的GetService方法,獲取到的都是同一個實例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
我們對IOperationSingleton註冊了三次,最後獲取兩次,大家要注意到我們獲取到的始終都是我們最後一次註冊的那個給了一個Guid的實例,前面的會被覆蓋。
2.3 實例生命週期之Tranisent
這次我們獲取到的 IOperationTransient 爲兩個不同的實例。
2.4 實例生命週期之Scoped
.NET Core 的 IServiceProvider 提供 CreateScope 產生一個新的 ServiceProvider 範圍,在這個範圍下的 Scope 標註的實例將只會是同一個實例。換句話來說:用 Scope 註冊的對象,在同一個 ServiceProvider 的 Scope下相當於單例。
同樣我們先分別註冊 IOperationScoped、IOperationTransient 和 IOperationSingletone 這三個實例,用對應的 Scoped、Transient、和 Singleton 生命週期。
1 2 3 4 |
|
接下來我們用ServiceProvider.CreateScope方法創建一個Scope
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
接下來
scope1: 200d1e63-d024-4cd3-88c9-35fdf5c00956,
transient1: fb35f570-713e-43fc-854c-972eed2fae52,
singleton1: da6cf60f-670a-4a86-8fd6-01b635f74225
scope2: 200d1e63-d024-4cd3-88c9-35fdf5c00956,
transient2: 2766a1ee-766f-4116-8a48-3e569de54259,
singleton2: da6cf60f-670a-4a86-8fd6-01b635f74225
如果再創建一個新的Scope運行,
scope1: 29f127a7-baf5-4ab0-b264-fcced11d0729,
transient1: 035d8bfc-c516-44a7-94a5-3720bd39ce57,
singleton1: da6cf60f-670a-4a86-8fd6-01b635f74225
scope2: 29f127a7-baf5-4ab0-b264-fcced11d0729,
transient2: 74c37151-6497-4223-b558-a4ffc1897d57,
singleton2: da6cf60f-670a-4a86-8fd6-01b635f74225
大家注意到上面我們一共得到了 4個Transient實例,2個Scope實例,1個Singleton實例。
這有什麼用?
如果在 Mvc 中用過 Autofac 的 InstancePerRequest 的同學就知道,有一些對象在一個請求跨越多個Action或者多個Service、Repository 的時候,比如最常用的 DBContext 它可以是一個實例。即能減少實例初始化的消耗,還能實現跨 Service 事務的功能。(注:在 ASP.NET Core 中所有用到 EF 的 Service 都需要註冊成 Scoped )
而實現這種功能的方法就是在整個reqeust請求的生命週期以內共用了一個Scope。
三、DI在ASP.NET Core中的應用
3.1在Startup類中初始化
ASP.NET Core 可以在 Startup.cs 的 ConfigureService 中配置 DI,大家看到 IServiceCollection 這個參數應該就比較熟悉了。
1 2 3 4 5 6 |
|
ASP.NET Core 的一些組件已經提供了一些實例的綁定,像 AddMvc 就是 Mvc Middleware 在 IServiceCollection 上添加的擴展方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
3.2 Controller中使用
一般可以通過構造函數或者屬性來實現注入,但是官方推薦是通過構造函數。這也是所謂的顯式依賴。
1 2 3 4 5 6 |
|
我們只要在控制器的構造函數裏面寫了這個參數,ServiceProvider 就會幫我們注入進來。這一步是在 Mvc 初始化控制器的時候完成的,我們後面在介紹到Mvc的時候會往細裏講。
3.3 View中使用
在 View 中需要用 @inject 再聲明一下,起一個別名。
1 2 3 4 5 6 7 8 9 10 |
|
3.4 通過HttpContext來獲取實例
HttpContext 下有一個 RequestedService 同樣可以用來獲取實例對象,不過這種方法一般不推薦。同時要注意GetService<>這是個範型方法,默認如果沒有添加Microsoft.Extension.DependencyInjection的using,是不用調用這個方法的。
1 |
|
四、如何替換其它的Ioc容器
Autofac 也是不錯的選擇,但我們首先要搞清楚爲什麼要替換掉默認的 DI 容器?,替換之後有什麼影響?.NET Core 默認的實現對於一些小型的項目完全夠用,甚至大型項目麻煩點也能用,但是會有些麻煩,原因在於只提供了最基本的 AddXXXX 方法來綁定實例關係,需要一個一個的添加。如果項目可能要添加好幾百行這樣的方法就需要尋找其他方法了。
熟悉 Autofac 的同學可能對下面這樣的代碼有印象。
1 2 3 |
|
這會給我們的初始化帶來一些便利性,我們來看看如何替換 Autofac 到 ASP.NET Core。我們只需要把 Startup 類裏面 ConfigureService 的返回值從 void 改爲 IServiceProvider 即可。而返回的則是一個 AutoServiceProvider。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
4.1 有何變化
其中很大的一個變化在於,Autofac 原來的一個生命週期 InstancePerRequest,將不再有效。正如我們前面所說的,整個 request 的生命週期被 ASP.NET Core 管理了,所以 Autofac 的這個將不再有效。我們可以使用 InstancePerLifetimeScope ,同樣是有用的,對應了我們 ASP.NET Core DI 裏面的 Scoped。
原文鏈接:https://www.cnblogs.com/jesse2013/p/di-in-aspnetcore.html