1. Ioc 與 DI
Ioc 和DI 這兩個詞大家都應該比較熟悉,這兩者已經在各種開發語言各種框架中普遍使用,成為框架中的一種基本設施了,
Ioc 是控制反轉, Inversion of Control 的縮寫,DI 是依賴注入,Inject Dependency 的縮寫,
所謂控制反轉,反轉的是類與類之間依賴的創建,型別A依賴于型別B時,不依賴于具體的型別,而是依賴于抽象,不在類A中直接 new 類B的物件,而是通過外部傳入依賴的型別物件,以實作類與類之間的解耦,所謂依賴注入,就是由一個外部容器對各種依賴型別物件進行管理,包括物件的創建、銷毀、狀態保持等,并在某一個型別需要使用其他型別物件的時候,由容器進行傳入,
下圖是一張網圖,是關于Ioc解耦比較經典的圖示程序了,至于依賴解耦的好處,就不在這里細講了,如果有對依賴注入基本概念不理解的,可以稍微搜索一下相關的文章,也可以參考 ASP.NET Core 依賴注入 | Microsoft Learn 官方檔案中的講解,
Ioc是一種設計思想,而DI是這種思想的具體實作,依賴注入是一種設計模式,是對面向物件編程五大基本原則中的依賴倒置原則的實踐,其中很重要的一個點就是 Ioc 容器的實作,
2. .NET Core 依賴注入的基本用法
在 .NET Core 平臺下,有一套自帶的輕量級Ioc框架,如果是ASP.NET Core專案,更是在使用主機的時候自動集成了進去,我們在startup類中的ConfigureServices方法中的代碼就是往容器中配置依賴注入關系,如果是控制臺專案的話,還需要自己去集成,除此之外,.NET 平臺下還有一些第三方依賴注入框架,如 Autofac、Unity、Castle Windsor等,
這里先不討論第三方框架的內容,先簡單介紹一下.Net Core平臺自帶的Ioc框架的使用,
2.1 服務
依賴項注入術語中,服務通常是指向其他物件提供服務的物件,既可以作為其他類的依賴項,也可能依賴于其他服務,服務是Ioc容器管理的物件,
2.2 服務生命周期
使用了依賴注入框架之后,所有我們注入到容器中的型別的創建、銷毀作業都由容器來完成,那么容器什么時候創建一個型別實體,什么時候銷毀一個型別實體呢?這就涉及到注入服務的生命周期了,根據我們的需要,我們可以向容器中注冊服務的時候,對服務的生命周期進行設定,服務的生命周期有以下三種:
(1) 單例 Singleton
注冊成單例模式的服務,整個應用程式生命周期以內只創建一個實體,在應用內第一個使用到該服務時創建,在應用程式停止時銷毀,
在某些情況下,對于某些特殊的類,我們需要注冊成單例模式,這可以減少實體初始化的消耗,還能實作跨 Service 事務的功能,
(2)范圍(或者作用域) Scoped
在同一個范圍內只初始化一個實體 ,在 web 應用中,可以理解為每一個 request 級別只創建一個實體,同一個 http request 會在一個 scope 內,
(3)多例 Tranisent
每一次使用到服務時都會創建一個新的實體,每一次對該依賴的獲取都是一個新實體,
2.3 服務注冊
在ASP.NET Core這樣的web應用框架中,在使用主機的時候就自動集成了依賴注入框架,之后我們可以通過 IServiceCollection 物件來注冊依賴注入關系,前面入口檔案一篇講過,.NET 6 之前可以在 Startup 類中的 ConfigureServices 方法中進行注冊,該方法傳入IServiceCollection引數,.NET 6 之后,可以通過 WebApplicationBuilder 物件的 Services屬性進行注冊,
服務注冊常用的方法如下:
-
Add 方法
通過引數 ServiceDescriptor 將服務型別、實作型別、生命周期等資訊傳入進去,是服務注冊最基本的方法,其中 ServiceDescriptor 引數又有多種變形,// 最基本的服務注冊方法,除此之外還有其他各種變形 builder.Services.Add(new ServiceDescriptor(typeof(IRabbit), typeof(Rabbit), ServiceLifetime.Transient)); builder.Services.Add(ServiceDescriptor.Scoped<IRabbit, Rabbit>()); builder.Services.Add(ServiceDescriptor.Singleton(typeof(IRabbit), (services) => new Rabbit()));
-
Add{lifetime}擴展方法
基于 Add 方法的擴展方法,包括以下幾種,每種都有多個多載:// 基于生命周期的擴展方法,以下為實體,正式開發中不可能將一個型別注冊為多個生命周期,會拋出例外 builder.Services.AddTransient<IRabbit, Rabbit>(); builder.Services.AddTransient(typeof(IRabbit), typeof(Rabbit)); builder.Services.AddScoped<IRabbit, Rabbit>(); builder.Services.AddSingleton<IRabbit, Rabbit>();
-
TryAdd{lifetime}擴展方法
對于 Add{lifetime} 方法的擴展,位于命名空間 Microsoft.Extensions.DependencyInjection.Extensions 下,
與 Add{lifetime} 方法相比,差別在于當使用 Add{lifetime} 方法將同樣的服務注冊了多次時,在使用 IEnumerable<{Service}> 決議服務時,就會產生多個實體的副本,這可能會導致一些意料之外的 bug,特別是單例生命周期的服務,// 同一個服務同一個實作注入多次 builder.Services.AddSingleton<IRabbit, Rabbit>(); builder.Services.AddSingleton<IRabbit, Rabbit>(); [ApiController] [Route("[controller]")] public class InjectTestController : ControllerBase { private readonly IEnumerable<IRabbit> _rabbits; public InjectTestController(IEnumerable<IRabbit> rabbits) { _rabbits = rabbits; [HttpGet("")] public Task InjectTest() { // 2個IRabbit實體 Console.WriteLine(_rabbits.Count()); var rabbit1 = _rabbits.First(); var rabbit2 = _rabbits.ElementAt(1); // 都是 Rabbit 型別 Console.WriteLine(rabbit1 is Rabbit); Console.WriteLine(rabbit2 is Rabbit); // 兩個實體不是同一個 Console.WriteLine(rabbit1 == rabbit2); return Task.CompletedTask; } }
呼叫介面后,列印輸出結果如下:
而使用 TryAdd{lifetime} 方法,當DI容器中已存在指定型別的服務時,則不進行任何操作;反之,則將該服務注入到DI容器中,
- TryAdd:對于 Add 方法
- TryAddTransient:對應AddTransient 方法
- TryAddScoped:對應AddScoped 方法
- TryAddSingleton:對應AddSingleton 方法
將服務注冊改成以下代碼:
builder.Services.AddTransient<IRabbit, Rabbit>(); // 由于上面已經注冊了服務型別 IRabbit,所以下面的代碼不不會執行任何操作(與生命周期無關) builder.Services.TryAddTransient<IRabbit, Rabbit>(); builder.Services.TryAddTransient<IRabbit, Rabbit1>();
在上面的控制器中新增以下方法:
[HttpGet(nameof(InjectTest1))] public Task InjectTest1() { // 只有1個IRabbit實體 Console.WriteLine(_rabbits.Count()); var rabbit1 = _rabbits.First(); // 都是 Rabbit 型別 Console.WriteLine(rabbit1 is Rabbit); return Task.CompletedTask; }
呼叫介面后,列印輸出結果如下:
-
TryAddEnumerable 方法
與 TryAdd 對應,區別在于TryAdd僅根據服務型別來判斷是否要進行注冊,而TryAddEnumerable則是根據服務型別和實作型別一同進行判斷是否要進行注冊,常常用于注冊同一服務型別的多個不同實作,
將服務注冊改成以下代碼:
builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IRabbit, Rabbit>()); builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IRabbit, Rabbit1>()); // 未進行任何操作,因為 IRabbit 服務的 Rabbit實作在上面已經注冊了 builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IRabbit, Rabbit>());
在上面的控制器新增一個方法:
[HttpGet(nameof(InjectTest2))] public Task InjectTest2() { // 2個IRabbit實體 Console.WriteLine(_rabbits.Count()); var rabbit1 = _rabbits.First(); var rabbit2 = _rabbits.ElementAt(1); // 第一個是 Rabbit 型別,第二個是 Rabbit1型別 Console.WriteLine(rabbit1 is Rabbit); Console.WriteLine(rabbit2 is Rabbit1); return Task.CompletedTask; }
呼叫介面后,控制臺列印如下:
-
Repalce 與 Remove 方法
當我們想要從Ioc容器中替換或是移除某些已經注冊的服務時,可以使用Replace和Remove,
// 將容器中注冊的IRabbit實作替換為 Rabbit1 builder.Services.Replace(ServiceDescriptor.Transient<IRabbit, Rabbit1>()); // 從容器中 IRabbit 注冊的實作 Rabbit1 builder.Services.Remove(ServiceDescriptor.Transient<IRabbit, Rabbit1>()); // 移除 IRabbit服務的所有注冊 builder.Services.RemoveAll<IRabbit>(); // 清空容器中的所有服務注冊 builder.Services.Clear();
以上是 .NET Core 框架自帶的 Ioc 容器的一些基本概念和依賴關系注入的介紹,下一章是注入到容器中的服務使用部分,
參考文章:
ASP.NET Core 依賴注入 | Microsoft Learn
理解ASP.NET Core - 依賴注入(Dependency Injection)
ASP.NET Core 系列:
目錄:ASP.NET Core 系列總結
上一篇:ASP.NET Core - 自定義中間件
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/544539.html
標籤:.NET技术
上一篇:原來建造者模式是這樣用的