­

.Net Core3.0依賴注入DI

  • 2019 年 10 月 31 日
  • 筆記

構建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支援指定三種生命周期如:

  1. Singleton 單例

  2. Scoped 範圍

  3. 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容器不能滿足項目需求,可以替換成第三方的如功能強大的Autofac。