.Net Core3.0依賴注入DI
構建ASP.NET Core應用程式的時候,依賴注入已成為了.NET Core的核心,這篇文章,我們理一理依賴注入的使用方法。
不使用依賴注入
首先,我們建立一個ASP.NET Core Mvc專案,定義個表達的愛服務介面,中國小夥類實現這個類如下:
public interface ISayLoveService { string SayLove(); } public class CNBoyService : ISayLoveService { public string SayLove() { return "安紅,我喜歡你"; } }
在LoveController 控制器中呼叫 ISayLoveService的SayLove方法。
public class LoveController : Controller { private ISayLoveService loveService; public IActionResult Index() { loveService = new CNBoyService(); //中國小夥對安紅的表達 ViewData["SayLove"] = loveService.SayLove(); return View(); } }
輸出如圖:
小結:LoveController控制器呼叫ISayLoveService服務的SayLove方法;我們的做法,直接在控制器去new CNBoyService()例項物件,也就是LoveController依賴ISayLoveService類。
思考:能不能有種模式,new例項不要在使用的時候進行建立,而是在外部或者有一個容器進行管理;這不就是ioc思想嗎?好處,程式碼的解耦、程式碼更好的維護等等。
使用依賴注入
上面的疑惑,答案是肯定的,有!並且ASP.NET Core 支援依賴關係注入 (DI) 軟體設計模式(當然也可以相容第三方)。我們還使用上面的程式碼,
服務註冊
在Startup類ConfigureServices方法中註冊服務容器中的依賴關係
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<ISayLoveService, CNBoyService>(); services.AddControllersWithViews(); }
在LoveControlle控制器中,通過建構函式注入
private readonly ISayLoveService loveService;
public LoveController(ISayLoveService loveService)
{
this.loveService = loveService; }
public IActionResult Index() {
ViewData["SayLove"] = loveService.SayLove();
return View(); }
LoveController 正在將ISayLoveService作為依賴項注入其建構函式中,然後在Index方法中使用它。
推薦:
-
將注入的依賴項分配給只讀欄位/屬性(以防止在方法內部意外為其分配另一個值)。
-
使用介面或基類抽象化依賴關係實現。
小結:在控制器中,還有幾種使用如:[FromServices] 標籤 、 HttpContext.RequestServices.GetService<T>();我們發現可以使用ASP.NET Core 提供了一個內建的服務容器 IServiceProvider。服務只需要在Startup.ConfigureServices 方法中註冊,然後在執行時將服務注入 到使用它的類的建構函式中。 框架負責建立依賴關係的例項,並在不再需要時對其進行處理。
思考:服務註冊的時候使用的是 AddSingleton,如services.AddSingleton<ISayLoveService, CNBoyService>();還有其他的嗎?
服務生命週期
服務註冊的時候,ASP.NET Core支援指定三種生命週期如:
-
Singleton 單例
-
Scoped 範圍
-
Transient 短暫的
Singleton 僅建立一個例項。該例項在需要它的所有元件之間共享。因此始終使用同一例項。
Scoped 每個範圍建立一個例項。在對應用程式的每個請求上都會建立一個範圍,因此每個請求將建立一次註冊為Scoped的任何元件。
Transient 在每次被請求時都會建立,並且永不共享。
為了能夠更好的裂解生命週期的概念,我們把上面程式碼稍作改動,做一個測試:
ISayLoveService 新增個屬性LoveId,型別為guid,
public interface ISayLoveService { Guid LoveId { get; } string SayLove(); } public interface ITransientSayLoveService : ISayLoveService { } public interface IScopedSayLoveService : ISayLoveService { } public interface ISingletonSayLoveService : ISayLoveService { } public interface ISingletonInstanceSayLoveService : ISayLoveService { }
BoyService也很簡單,在建構函式中傳入一個Guid,並對它進行賦值。
public class BoyService : ITransientSayLoveService, IScopedSayLoveService, ISingletonSayLoveService, ISingletonInstanceSayLoveService { public BoyService():this(Guid.NewGuid()) { } public BoyService(Guid id) { LoveId = id; } public Guid LoveId { get; private set; } public string SayLove() { return LoveId.ToString(); } }
每個實現類的建構函式中,我們都產生了一個新的guid,通過這個GUID,我們可以判斷這個類到底重新執行過建構函式沒有.
服務註冊程式碼如下:
public void ConfigureServices(IServiceCollection services) { //生命週期設定為Transient,因此每次都會建立一個新例項。 services.AddTransient<ITransientSayLoveService, BoyService>(); services.AddScoped<IScopedSayLoveService, BoyService>(); services.AddSingleton<ISingletonSayLoveService, BoyService>(); services.AddSingleton<ISingletonInstanceSayLoveService>(new BoyService(Guid.Empty)); services.AddControllersWithViews(); }
在LifeIndex方法中多次呼叫ServiceProvider的GetService方法,獲取到的都是同一個例項。
public IActionResult LifeIndex() { ViewData["TransientSayLove1"] = HttpContext.RequestServices.GetService<ITransientSayLoveService>().SayLove(); ViewData["ScopedSayLove1"] = HttpContext.RequestServices.GetService<IScopedSayLoveService>().SayLove(); ViewData["SingletonSayLove1"] = HttpContext.RequestServices.GetService<ISingletonSayLoveService>().SayLove(); ViewData["SingletonInstanceSayLove1"] = HttpContext.RequestServices.GetService<ISingletonInstanceSayLoveService>().SayLove(); //同一個HTTP請求 ,在從容器中獲取一次 ViewData["TransientSayLove2"] = HttpContext.RequestServices.GetService<ITransientSayLoveService>().SayLove(); ViewData["ScopedSayLove2"] = HttpContext.RequestServices.GetService<IScopedSayLoveService>().SayLove(); ViewData["SingletonSayLove2"] = HttpContext.RequestServices.GetService<ISingletonSayLoveService>().SayLove(); ViewData["SingletonInstanceSayLove2"] = HttpContext.RequestServices.GetService<ISingletonInstanceSayLoveService>().SayLove(); return View(); }
我們編寫view頁面,來展示這些資訊如下:
@{ ViewData["Title"] = "LifeIndex"; } <div class="row"> <div class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">Operations</h2> </div> <div class="panel-body"> <h3>獲取第一次</h3> <dl> <dt>Transient1</dt> <dd>@ViewData["TransientSayLove1"] </dd> <dt>Scoped1</dt> <dd>@ViewData["ScopedSayLove1"]</dd> <dt>Singleton1</dt> <dd>@ViewData["SingletonSayLove1"] </dd> <dt>Instance1</dt> <dd>@ViewData["SingletonInstanceSayLove1"]</dd> </dl> <h3>獲取第二次</h3> <dl> <dt>Transient2</dt> <dd>@ViewData["TransientSayLove2"]</dd> <dt>Scoped2</dt> <dd>@ViewData["ScopedSayLove2"]</dd> <dt>Singleton2</dt> <dd>@ViewData["SingletonSayLove2"]</dd> <dt>Instance2</dt> <dd>@ViewData["SingletonInstanceSayLove2"]</dd> </dl> </div> </div> </div>
執行程式碼第一次輸出:
我們發現,在一次請求中,發現單例、範圍的生命週期的guid 沒有變化,說明分別用的是同一個物件,而瞬態guid不同,說明物件不是一個。
重新整理之後,檢視執行效果
我們發現通過重新整理之後,單例模式的guid還是跟首次看到的一樣,其他的都不同;
總結:如果您將元件A註冊為單例,則它不能依賴已註冊“作用域”或“瞬態”生存期的元件。一般而言:元件不能依賴壽命短於其壽命的元件。如果預設的DI容器不能滿足專案需求,可以替換成第三方的如功能強大的Autofa