.NET Core ASP.NET Core Basic 1-2 控制反轉與依賴注入

  • 2019 年 10 月 3 日
  • 筆記

.NET Core ASP.NET Core Basic 1-2

本節內容為控制反轉與依賴注入

簡介

控制反轉IOC

這個內容事實上在我們的C#高級篇就已經有所講解,控制反轉是一種設計模式,你可以這樣理解控制反轉,假設有一個人他有一部A品牌手機,他用手機進行聽歌、打遊戲,那麼你可以創建一個手機類和一個人類

class APhone : IPhone  {      public string Owner{get;set;}      public Phone(string own)      {          Owner = own;      }      void Play()      {          //省略      }      void Music()      {          //省略      }  }  class Man  {      public string Name{get;set;}      void Game()      {          var p = new APhone(Name);          p.Play();      }  }

事實上這段程式碼的耦合度是比較高的?它使用的是正轉,也就是我需要什麼東西的時候我就自己創建一個這個東西。為什麼說他不好呢,如果有一天這個人決定再也不使用A品牌手機了,他決定以後只使用B品牌。那麼也就意味著整個的Man類使用過APhone類的地方都需要更改。這是一個非常麻煩的事情,我們這個時候就需要運用我們的IOC控制反轉了。我們將實例或者是需要使用的對象的創建交給你的調用者,自己只負責使用,其它人丟給你依賴的這個過程理解為注入。

控制反轉的核心就是——原本我保存使用我自己的東西,現在我把控制權交給我的上級,我需要使用的時候再向他要。這個時候,介面的作用不言而喻,A繼承了Phone介面,B也繼承了,假定我們一開始就使用Phone介面去創建不同的A,B對象,那麼是不是可以有效的切換AB對象呢?

依賴注入

依賴注入體現的是一個IOC(控制反轉),它非常的簡單,我們之前的Man類程式碼中使用的是正轉的方式,也就是我要一個對象,那麼我就創建一個。現在我們使用依賴注入就是將我們對這個對象的控制權交給上一級介面,也就成為了這種,我想要一個對象,我就向上級發出請求,上級就給我創建了一個對象。我們通常使用構造函數注入的方式進行依賴的注入。

上文的程式碼就會變成

class Man  {      private readonly IPhone _phone;      public Man(IPhone phone)      {          _phone = phone;      }  }

假設這個時候你需要將手機換成B品牌,那麼只需要再注入的地方傳入B品牌的對象即可了。

容器

但是現在又出現了一個新的問題,假設說這個類有100個使用該介面的依賴,如果,我們是不是要在100個地方做這樣的事情? 控制是反轉了,依賴的創建也移交到了外部。現在的問題是依賴太多,我們需要一個地方統一管理系統中所有的依賴,這個時候,我們就使用容器進行集中的管理

容器負責兩件事情:

  • 綁定服務與實例之間的關係
  • 獲取實例,並對實例進行管理(創建與銷毀)

使用

說了那麼多,我們如何在.NET Core中使用我們的依賴注入呢?這裡我們針對的是所有的.NET Core的應用,在.NET Core中依賴注入的核心分為兩個組件:位於Microsoft.Extensions.DependencyInjection命名空間下的IServiceCollection和 IServiceProvider。

其中

  • IServiceCollection 負責註冊
  • IServiceProvider 負責提供實例

在默認的容器ServiceCollection中有三個方法

  • .AddTransient<I,C>()
  • .AddSingleton<I,C>()
  • .AddScoped<I,C>()

這裡就不得不提一下我們依賴注入的三種生命周期了

  • Singleton指的是單例模式,也就是說,在整個程式運轉期間只會生成一次
  • Transient指每一次GetService都會創建一個新的實例
  • Scope指在同一個Scope內只初始化一個實例 ,可以理解為( 每一個request級別只創建一個實例,同一個http request會在一個 scope內)

我們可以嘗試使用控制台項目來模擬依賴注入的原理,也就是說我們直接從容器獲取我們對象實例,並且我們使用Guid進行唯一性的標記。

//創建三個代表不同生命周期的介面      interface IPhoneScope      {          Guid Guid { get; }      }      interface IPhoneSingleton      {          Guid Guid { get; }      }      interface IPhoneTransient      {          Guid Guid { get; }      }      //實現的類      class PhoneService:IPhoneScope,IPhoneSingleton,IPhoneTransient      {          public PhoneService()          {              this._guid = Guid.NewGuid();          }            public PhoneService(Guid guid)          {              this._guid = guid;          }            private Guid _guid;            public Guid Guid => this._guid;      }

然後,在我們的主函數中

namespace DI_AND_IOC  {      class Program      {          static void Main(string[] args)          {              //注入服務              var services = new ServiceCollection()                  .AddScoped<IPhoneScope, PhoneService>()                  .AddTransient<IPhoneTransient, PhoneService>()                  .AddSingleton<IPhoneSingleton, PhoneService>();              //構造服務              var provider = services.BuildServiceProvider();              using (var scope = provider.CreateScope())              {                  var p = scope.ServiceProvider;                  var scopeobj1 = p.GetService<IPhoneScope>();                  var transient1 = p.GetService<IPhoneTransient>();                  var singleton1 = p.GetService<IPhoneSingleton>();                    var scopeobj2 = p.GetService<IPhoneScope>();                  var transient2 = p.GetService<IPhoneTransient>();                  var singleton2 = p.GetService<IPhoneSingleton>();                    Console.WriteLine(                      $"scope1: {scopeobj1.Guid},n" +                      $"transient1: {transient1.Guid}, n" +                      $"singleton1: {singleton1.Guid}n");                    Console.WriteLine($"scope2: {scopeobj2.Guid}, n" +                                    $"transient2: {transient2.Guid},n" +                                    $"singleton2: {singleton2.Guid}n");              }              //創建不同的scope              using (var scope = provider.CreateScope())              {                  var p = scope.ServiceProvider;                  var scopeobj3 = p.GetService<IPhoneScope>();                  var transient3 = p.GetService<IPhoneTransient>();                  var singleton3 = p.GetService<IPhoneSingleton>();                  Console.WriteLine($"scope3: {scopeobj3.Guid}, n" +                                    $"transient3: {transient3.Guid},n" +                                    $"singleton3: {singleton3.Guid}");              }          }      }  }

你應該會得到類似以下的數據

scope1: 096d38e5-0c7b-4e50-9c79-241fb18a56ed,  transient1: 289ebd11-8159-4f22-b53e-ed738a317313,  singleton1: b453b7f5-3594-4b66-99c8-a72763abaa83    scope2: 096d38e5-0c7b-4e50-9c79-241fb18a56ed,  transient2: 212ad420-e54c-4dd6-9214-abe91aacdd9c,  singleton2: b453b7f5-3594-4b66-99c8-a72763abaa83    scope3: 688b6ffd-a8c1-47f4-a20a-872c2285d67c,  transient3: 3d09997d-fffb-43d1-9e53-ccf9771c819d,  singleton3: b453b7f5-3594-4b66-99c8-a72763abaa83

可以發現,singleton對象是不會發生改變的,而scope對象在創建新的scope之後就發生了改變,而transient對象每一次請求都在發生改變。

需要注意的是,在控制台項目使用容器服務需要引入 *** Microsoft.Extensions.DependencyInjection *** 程式集,你可以在引入中導入該dll

通過對注入服務的生命周期管控,在一些ASP.NET Core項目中,有些類(服務)有可能跨越了多個Action或者Controller,那麼我們正確的使用生命周期,我們可以儘可能的節省記憶體,即能減少實例初始化的消耗。

在ASP.NET Core中的使用

在ASP.NET Core中,我們使用依賴注入非常的簡單,在StartUp類中的ConfigureServices方法中已經為我們構建好了容器,我們只需要做類似於這樣的操作

services.AddScoped<IPhoneScope, PhoneService>();  services.AddDbContext<DbContext>();  services.AddMVC();

如果你需要在控制器中注入服務,官方的推薦方案是使用構造函數注入

public IPhoneScope _ips;  public Controller(IPhoneScope ips)  {      _ips = ips;  }

特別的,你如果使用MVC的Razor頁面進行注入的話,那麼輸入以下指令

@inject IPhoneScope  ips

如果我的文章幫助了您,請您在github.NETCoreGuide項目幫我點一個star,在部落格園中點一個關注和推薦。

Github

BiliBili主頁

WarrenRyan'sBlog

部落格園

Tags: