在.NET Core的依賴注入框架中,服務註冊的資訊將會被封裝成ServiceDescriptor物件,而這些物件都會儲存在IServiceCollection介面型別表示的集合中,另外,IServiceCollection介面型別預設使用的實現型別為ServiceCollection。這樣來看,實際上服務註冊這回事,它就是一個將建立的ServiceDescriptor物件新增到IServiceCollection介面型別集合中的過程。
通常一個專案,都是由大量的物件相互共同作業而構建成的應用程式。所以對於使用依賴注入框架的應用程式而言,需要進行大量的服務註冊,並且其中會存在不同形式的註冊需求。.NET Core為此提供了大量不同形式的服務註冊方法,為了方便使用,它將這些方法都定義為了IServiceCollection介面的擴充套件方法,這些擴充套件方法主要分佈在
ServiceCollectionServiceExtensions和ServiceCollectionDescriptorExtensions這兩個類中。這兩個型別都可以實現服務的註冊,但是具有如下不同的側重點:
個人覺得前者更加簡單易用,後者則設定靈活且功能豐富,下面將圍繞這兩個型別中的服務註冊方法進行介紹。
該型別中關於服務註冊方法的命名,通常是以固定字元「Add」作為字首,「Add」後面則是會加上不同的生命週期模式名稱。該型別中的註冊方法也屬於實際開發場景中較為常用的一種,因為使用這種方式的好處是:無需在服務註冊時傳入一個具體的生命週期,而是根據服務對生命週期模式的需求,選擇與模式名稱相同的方法即可。
例如,需要建立生命週期模式為Singleton的服務,那麼與之對應的註冊方法就是AddSingleton。並且可以根據AddSingleton方法的過載選擇任意一種提供服務範例的方式。一般最常用的過載形式是,使用對應泛型方法傳入服務型別和實現型別,例如下面的使用方式:
1 var collection = new ServiceCollection();
2 collection.AddSingleton<IFooBar, FooBar>();
該型別的註冊方法主要都是根據3種生命週期模式而設計,並結合ServiceDescriptor型別中3種提供服務範例的方式進行擴充套件,從而延申出了很多過載形式,更加方便的為應用程式提供服務註冊。如果想要具體瞭解每個生命週期模式的不同過載可以參考官方說明:
相比ServiceCollectionServiceExtensions型別中的方法而言,雖然ServiceCollectionDescriptorExtensions型別從名稱上只有一詞之差,但是它的方法涉及範圍更加豐富。該型別不僅具備服務的註冊,還具備了對服務註冊資訊的「增刪改」,並且包含針對服務註冊重複性判斷的方法,接下來則針對該型別中的方法進行一個介紹。
該方法需要我們將服務註冊的資訊建立為一個ServiceDescriptor物件,並將該物件作為引數傳入Add方法,以此形式作為服務註冊。該方法有兩種過載形式,一種是新增單個ServiceDescriptor物件,另一種是可以新增多個ServiceDescriptor物件的集合,對於該方法的呼叫範例可以參考如下程式碼:
1 //服務註冊資訊集合
2 var serviceCollection = new ServiceCollection();
3
4 //新增「單個」服務註冊資訊物件
5 ServiceDescriptor descriptor = new ServiceDescriptor(typeof(IFooBar),typeof(FooBar), ServiceLifetime.Singleton);
6 serviceCollection.Add(descriptor);
7
8 //新增「多個」服務註冊資訊物件
9 List<ServiceDescriptor> descriptorList = new List<ServiceDescriptor>()
10 {
11 new ServiceDescriptor(typeof(IAnimal),typeof(Dog), ServiceLifetime.Singleton),
12 new ServiceDescriptor(typeof(IEmployee),typeof(Programmer), ServiceLifetime.Singleton)
13 };
14 serviceCollection.Add(descriptorList);
.NET Core依賴注入框架中支援對同一服務型別進行多次註冊。因為後續介紹的某些方法會針對這種重複多次的註冊方式有相應的處理邏輯,所以在介紹那些方法之前,首先來介紹下服務重複註冊這個行為有哪些特點。
1.對於同一個服務型別進行多次註冊,使用GetService獲取範例時,只會獲取一個最近註冊的服務範例。例如下圖的程式碼範例中,分別針對同一服務型別IAnimal進行了多次註冊,其中實現型別包含:Dog、Cat、Pig,但最後獲取的範例只有Pig一個。
2.對於同一個服務型別進行多次註冊,如果要獲取該服務型別註冊的所有服務範例,則可以使用GetServices方法來獲取。例如下圖的程式碼範例中,分別針對同一服務型別Base進行了多次註冊,在通過呼叫GetServices方法後,將服務型別Base註冊的所有服務範例都儲存到了一個集合中。
之所以在在此處介紹的標題為TryAdd形式的方法,是因為除了TryAdd方法本身之外,還有以TryAdd作為方法名稱字首的方法,分別是:TryAdd{生命週期模式}和TryAddEnumerable,並且3個方法都是圍繞同一個主題,即避免服務重複註冊。它們在呼叫的時候都會去檢查服務型別是否已經註冊過,如果已經註冊了,則放棄當前方法的註冊。TryAdd方法本身和TryAdd{生命週期模式}處理的邏輯基本是一致的,TryAddEnumerable處理會略有不同,下面針對這3類TryAdd形式的方法進行逐一介紹。
TryAdd
該方法需要我們將服務註冊的資訊建立為一個ServiceDescriptor物件,類似於Add方法,只不過在Add的基礎上加了服務重複註冊的判斷邏輯,使用方式如下圖範例:
該範例執行的結果:最終只會獲取到第一次註冊的服務範例,因為當第二次呼叫TryAdd時就發現了已經存在相同服務型別的註冊,此時它會放棄註冊行為。
TryAdd{生命週期模式}
TryAdd{生命週期模式}型別的方法可以看作是TryAdd方法的一種延申形式,處理邏輯和TryAdd方法一致。但是使用起來更加的便捷,因為它是根據3種生命週期模式擴充套件而來的版本,使用該型別方法註冊時無需指定生命週期模式引數,而是根據生命週期需求選擇對應的方法名來決定生命週期模式。
以上是TryAdd{生命週期模式}類方法的使用範例,可以看出相比TryAdd方法而言,該方法更加方便,我們無需建立ServiceDescriptor物件和指定生命週期模式。該範例以Singleton模式為例,其他兩種模式的呼叫與此一致,此處就不一一演示了。
TryAddEnumerable
對於TryAddEnumerable方法判斷服務重複性的邏輯而言,不在是和前兩者的方法一樣:僅僅使用服務型別這一個條件來判斷重複性。TryAddEnumerable方法在檢查重複性時會同時考慮「服務型別」和「實現型別」。如果發現某個服務註冊資訊物件的「服務型別」和「實現型別」,與當前即將要註冊服務資訊的「服務型別」和「實現型別」相同時,TryAddEnumerable方法會放棄當前的註冊,以避免出現對於這種情況的重複註冊。下面基於這個邏輯通過程式碼範例來證明這一點。
對於上面的範例而言,都是針對同一服務型別使用TryAddEnumerable方法的註冊。其中對於第三次進行的Dog的註冊而言,此時的ServiceCollection服務註冊集合中已經存在了一個相同服務型別的註冊,並且已存在的這個服務註冊的實現型別也與之相同,所以第三次進行的註冊被TryAddEnumerable方法認定為一個重複性的註冊,故沒有新增到ServiceCollection集合中。而Cat註冊時雖然已經存在了相同服務型別的IAnimal,但是沒有服務型別和實現型別同時相同的Cat註冊,即不滿足TryAddEnumerable的重複性判斷條件,所以該服務註冊會被新增到ServiceCollection服務註冊集合中。
.NET Core依賴注入框架對於服務註冊的行為,實際上就是將ServiceDescriptor(服務註冊資訊描述物件)新增到IServiceCollection集合中的過程。所以對於一個集合而言除了用於服務註冊的新增方法之外,還有一些維護性的方法,用於對IServiceCollection中的服務註冊進行:刪除、替換、清空等操作,這些方法的來源主要來自兩點:
下面通過程式碼範例對一些常用的方法進行演示,並通過註釋對方法使用的細節進行強調:
1 var serviceCollection = new ServiceCollection();
2 serviceCollection.AddSingleton<IAnimal, Dog>();
3 serviceCollection.AddSingleton<Base, Pig>();
4
5 //替換:將舊的ServiceDescriptor物件刪除,在新增一個新的ServiceDescriptor物件
6 var catDescriptor = ServiceDescriptor.Scoped<IAnimal, Cat>();
7 serviceCollection.Replace(catDescriptor);
8
9 //刪除:刪除指定服務型別的註冊
10 serviceCollection.RemoveAll<Base>();
11
12 //清空ServiceCollection集合中所有的服務註冊資訊物件
13 serviceCollection.Clear();
.NET Core依賴注入框架雖然為我們提供了豐富的服務註冊方法,但是對於大型的專案而言,服務註冊將成為一種高頻次且重複性的枯燥工作,那麼對於這樣的一種情形而言,有沒有一種自動化的註冊方式呢?答案是有的,就是藉助使用一個第三方開源的NetCore.AutoRegisterDi元件來實現服務的自動註冊。下面將通過一個範例來演示如何使用NetCore.AutoRegisterDi元件。
通過NuGet下載元件的方式有很多,你可以用不同的下載,本範例通過利用VS中視覺化的NuGet介面進行下載。
NetCore.AutoRegisterDi元件自動化註冊的前提是,要求服務註冊的實現型別具有某一種特徵,該元件會基於這種特徵查詢出與該特徵匹配的所有型別,然後對這些符合特徵的型別進行服務註冊,最終注入到依賴它的型別中。本範例中服務註冊的實現型別所採用的特徵是:所有服務實現型別名稱都是以「Service」結尾。
1 using System;
2
3 namespace DependencyInjectionDemo
4 {
5 //服務型別
6 public interface IComputer
7 {
8 string SayHi(); //打招呼
9 }
10
11 //服務實現型別
12 public class ComputerService : IComputer
13 {
14 public string SayHi()
15 {
16 return "你好,我是戴爾電腦。";
17 }
18 }
19
20 }
本範例中是將註冊的服務提供給MVC中的控制器物件,控制器物件對依賴的服務提供以建構函式形式的注入方式,如下程式碼所示。
1 public class HomeController : Controller
2 {
3 private IComputer _computer;
4
5 public HomeController(IComputer computer)
6 {
7 _computer = computer;
8 }
9
10 public IActionResult Index()
11 {
12 ViewBag.Msg = _computer.SayHi();
13 return View();
14 }
15
16 }
以ASP.NET Core的專案為例,我們需要在Startup類的ConfigureServices方法中新增如下的程式碼:
1 //獲取服務實現型別所在的程式集,一般都在實體層
2 var modelAssembly = Assembly.Load("DependencyInjectionDemo");
3
4 //服務自動註冊
5 services.RegisterAssemblyPublicNonGenericClasses(modelAssembly)
6 .Where(c => c.Name.EndsWith("Service"))
7 .AsPublicImplementedInterfaces(ServiceLifetime.Scoped);
其中RegisterAssemblyPublicNonGenericClasses方法用於獲取某個程式集中的所有型別(在實際的專案中我們的服務實現型別一般都定義在實體層),在RegisterAssemblyPublicNonGenericClasses方法呼叫之後,根據為「服務實現型別」定義的特徵來獲取相應的型別,此處我們使用Where方法結合特徵(以Service結尾的類)篩選出相應的型別。AsPublicImplementedInterfaces方法表示,將針對前面兩個方法篩選出的所有公共實現型別,使用指定的生命週期模式結合服務型別進行服務註冊。
在完成以上的步驟後,後續對於應用程式的服務註冊,都將由NetCore.AutoRegisterDi元件自動完成,那麼這樣一來,對於後續使用依賴注入框架而言,我們僅僅只需要為依賴的服務定義建構函式的注入形式即可。
學習感悟:學習就像是燒開水,燒到30度、60度、90度,燒的次數在多,沒有燒到100度沸點則都是白費。