1. 程式人生 > >ASP.NET Core中的依賴注入(4): 建構函式的選擇與服務生命週期管理

ASP.NET Core中的依賴注入(4): 建構函式的選擇與服務生命週期管理

ServiceProvider最終提供的服務例項都是根據對應的ServiceDescriptor建立的,對於一個具體的ServiceDescriptor物件來說,如果它的ImplementationInstance和ImplementationFactory屬性均為Null,那麼ServiceProvider最終會利用其ImplementationType屬性返回的真實型別選擇一個適合的建構函式來建立最終的服務例項。我們知道服務服務的真實型別可以定義了多個建構函式,那麼ServiceProvider針對建構函式的選擇會採用怎樣的策略呢?

目錄
一、建構函式的選擇
二、生命週期管理
    ServiceScope與ServiceScopeFactory
    三種生命週期管理模式
    服務例項的回收

一、建構函式的選擇

如果ServiceProvider試圖通過呼叫建構函式的方式來建立服務例項,傳入建構函式的所有引數必須先被初始化,最終被選擇出來的建構函式必須具備一個基本的條件:ServiceProvider能夠提供建構函式的所有引數。為了讓讀者朋友能夠更加真切地理解ServiceProvider在建構函式選擇過程中採用的策略,我們不讓也採用例項演示的方式來進行講解。

我們在一個控制檯應用中定義了四個服務介面(IFoo、IBar、IBaz和IGux)以及實現它們的四個服務類(Foo、Bar、Baz和Gux)。如下面的程式碼片段所示,我們為Gux定義了三個建構函式,引數均為我們定義了服務介面型別。為了確定ServiceProvider最終選擇哪個建構函式來建立目標服務例項,我們在建構函式執行時在控制檯上輸出相應的指示性文字。

   1: public interface IFoo {}
   2: public interface IBar {}
   3: public interface IBaz {}
   4: public interface IGux {}
   5:  
   6: public class Foo : IFoo {}
   7: public class Bar : IBar {}
   8: public class Baz : IBaz {}
   9: public class Gux : IGux
  10: {
  11:     public Gux(IFoo foo)
  12:     {
  13:         Console.WriteLine("Gux(IFoo)");
  14:     }
  15:  
  16:     public Gux(IFoo foo, IBar bar)
  17:     {
  18:         Console.WriteLine("Gux(IFoo, IBar)");
  19:     }
  20:  
  21:     public Gux(IFoo foo, IBar bar, IBaz baz)
  22:     {
  23:         Console.WriteLine("Gux(IFoo, IBar, IBaz)");
  24:     }
  25: }

我們在作為程式入口的Main方法中建立一個ServiceCollection物件並在其中新增針對IFoo、IBar以及IGux這三個服務介面的服務註冊,針對服務介面IBaz的註冊並未被新增。我們利用由它建立的ServiceProvider來提供針對服務介面IGux的例項,究竟能否得到一個Gux物件呢?如果可以,它又是通過執行哪個建構函式建立的呢?

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {       
   5:         new ServiceCollection()
   6:             .AddTransient<IFoo, Foo>()
   7:             .AddTransient<IBar, Bar>()
   8:             .AddTransient<IGux, Gux>()
   9:             .BuildServiceProvider()
  10:             .GetServices<IGux>();
  11:     }
  12: }

對於定義在Gux中的三個建構函式來說,ServiceProvider所在的ServiceCollection包含針對介面IFoo和IBar的服務註冊,所以它能夠提供前面兩個建構函式的所有引數。由於第三個建構函式具有一個型別為IBaz的引數,這無法通過ServiceProvider來提供。根據我們上面介紹的第一個原則(ServiceProvider能夠提供建構函式的所有引數),Gux的前兩個建構函式會成為合法的候選建構函式,那麼ServiceProvider最終會選擇哪一個呢?

在所有合法的候選建構函式列表中,最終被選擇出來的建構函式具有這麼一個特徵:每一個候選建構函式的引數型別集合都是這個建構函式引數型別集合的子集。如果這樣的建構函式並不存在,一個型別為InvalidOperationException的異常會被跑出來。根據這個原則,Gux的第二個建構函式的引數型別包括IFoo和IBar,而第一個建構函式僅僅具有一個型別為IFoo的引數,最終被選擇出來的會是Gux的第二個建構函式,所有執行我們的例項程式將會在控制檯上產生如下的輸出結果。

   1: Gux(IFoo, IBar)

接下來我們對例項程式略加改動。如下面的程式碼片段所示,我們只為Gux定義兩個建構函式,它們都具有兩個引數,引數型別分別為IFoo&IBar和IBar&IBaz。在Main方法中,我們將針對IBaz/Baz的服務註冊新增到建立的ServiceCollection上。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {       
   5:         new ServiceCollection()
   6:             .AddTransient<IFoo, Foo>()
   7:             .AddTransient<IBar, Bar>()
   8:             .AddTransient<IBaz, Baz>()
   9:             .AddTransient<IGux, Gux>()
  10:             .BuildServiceProvider()
  11:             .GetServices<IGux>();
  12:     }
  13: }
  14:  
  15: public class Gux : IGux
  16: {
  17:     public Gux(IFoo foo, IBar bar) {}
  18:     public Gux(IBar bar, IBaz baz) {}
  19: }

對於Gux的兩個建構函式,雖然它們的引數均能夠由ServiceProvider來提供,但是並沒有一個建構函式的引數型別集合能夠成為所有有效建構函式引數型別集合的超集,所以ServiceProvider無法選擇出一個最佳的建構函式。如果我們執行這個程式,一個InvalidOperationException異常會被丟擲來,控制檯上將呈現出如下所示的錯誤訊息。

   1: Unhandled Exception: System.InvalidOperationException: Unable to activate type 'Gux'. The following constructors are ambigious:
   2: Void .ctor(IFoo, IBar)
   3: Void .ctor(IBar, IBaz)
   4: ...

二、生命週期管理

生命週期管理決定了ServiceProvider採用怎樣的方式建立和回收服務例項。ServiceProvider具有三種基本的生命週期管理模式,分別對應著列舉型別ServiceLifetime的三個選項(Singleton、Scoped和Transient)。對於ServiceProvider支援的這三種生命週期管理模式,Singleton和Transient的語義很明確,前者(Singleton)表示以“單例”的方式管理服務例項的生命週期,意味著ServiceProvider物件多次針對同一個服務型別所提供的服務例項實際上是同一個物件;而後者(Transient)則完全相反,對於每次服務提供請求,ServiceProvider總會建立一個新的物件。那麼Scoped又體現了ServiceProvider針對服務例項怎樣的生命週期管理方式呢?

ServiceScope與ServiceScopeFactory

ServiceScope為某個ServiceProvider物件圈定了一個“作用域”,列舉型別ServiceLifetime中的Scoped選項指的就是這麼一個ServiceScope。在依賴注入的應用程式設計介面中,ServiceScope通過一個名為IServiceScope的介面來表示。如下面的程式碼片段所示,繼承自IDisposable介面的IServiceScope具有一個唯一的只讀屬性ServiceProvider返回確定這個服務範圍邊界的ServiceProvider。表示ServiceScope由它對應的工廠ServiceScopeFactory來建立,後者體現為具有如下定義的介面IServiceScopeFactory。

   1: public interface IServiceScope : IDisposable
   2: {
   3:     IServiceProvider ServiceProvider { get; }
   4: }
   5:  
   6: public interface IServiceScopeFactory
   7: {
   8:     IServiceScope CreateScope();
   9: }

若要充分理解ServiceScope和ServiceProvider之間的關係,我們需要簡單瞭解一下ServiceProvider的層級結構。除了直接通過一個ServiceCollection物件建立一個獨立的ServiceProvider物件之外,一個ServiceProvider還可以根據另一個ServiceProvider物件來建立,如果採用後一種建立方式,我們指定的ServiceProvider與建立的ServiceProvider將成為一種“父子”關係。

   1: internal class ServiceProvider : IServiceProvider, IDisposable
   2: {
   3:     private readonly ServiceProvider _root;
   4:     internal ServiceProvider(ServiceProvider parent)
   5:     {
   6:         _root = parent._root;
   7:     }
   8:     //其他成員
   9: }

3-11雖然在ServiceProvider在建立過程中體現了ServiceProvider之間存在著一種樹形化的層級結構,但是ServiceProvider物件本身並沒有一個指向“父親”的引用,它僅僅會保留針對根節點的引用。如上面的程式碼片段所示,針對根節點的引用體現為ServiceProvider類的欄位_root。當我們根據作為“父親”的ServiceProvider建立一個新的ServiceProvider的時候,父子均指向同一個“根”。我們可以將建立過程中體現的層級化關係稱為“邏輯關係”,而將ServiceProvider物件自身的引用關係稱為“物理關係”,右圖清楚地揭示了這兩種關係之間的轉化。

由於ServiceProvider自身是一個內部型別,我們不能採用呼叫建構函式的方式根據一個作為“父親”的ServiceProvider建立另一個作為“兒子”的ServiceProvider,但是這個目的可以間接地通過建立ServiceScope的方式來完成。如下面的程式碼片段所示,我們首先建立一個獨立的ServiceProvider並呼叫其GetService<T>方法獲得一個ServiceScopeFactory物件,然後呼叫後者的CreateScope方法建立一個新的ServiceScope,它的ServiceProvider就是前者的“兒子”。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         IServiceProvider serviceProvider1 = new ServiceCollection().BuildServiceProvider();
   6:         IServiceProvider serviceProvider2 = serviceProvider1.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
   7:  
   8:         object root = serviceProvider2.GetType().GetField("_root", BindingFlags.Instance| BindingFlags.NonPublic).GetValue(serviceProvider2);
   9:         Debug.Assert(object.ReferenceEquals(serviceProvider1, root));        
  10:     }
  11: }

如果讀者朋友們希望進一步瞭解ServiceScope的建立以及它和ServiceProvider之間的關係,我們不妨先來看看作為IServiceScope介面預設實現的內部型別ServiceScope的定義。如下面的程式碼片段所示,ServiceScope僅僅是對一個ServiceProvider物件的簡單封裝而已。值得一提的是,當ServiceScope的Dispose方法被呼叫的時候,這個被封裝的ServiceProvider的同名方法同時被執行。

   1: {
   2:     private readonly ServiceProvider _scopedProvider;
   3:     public ServiceScope(ServiceProvider scopedProvider)
   4:     {
   5:         this._scopedProvider = scopedProvider;
   6:     }
   7:  
   8:     public void Dispose()
   9:     {
  10:         _scopedProvider.Dispose();
  11:     }
  12:  
  13:     public IServiceProvider ServiceProvider
  14:     {
  15:         get {return _scopedProvider; }
            
           

相關推薦

ASP.NET Core依賴注入4: 建構函式選擇服務生命週期管理

ServiceProvider最終提供的服務例項都是根據對應的ServiceDescriptor建立的,對於一個具體的ServiceDescriptor物件來說,如果它的ImplementationInstance和ImplementationFactory屬性均為Null,那麼ServiceProvider

ASP.NET CORE 使用 SESSION 轉載

Session 是儲存使用者和 Web 應用的會話狀態的一種方法,ASP.NET Core 提供了一個用於管理會話狀態的中介軟體。在本文中我將會簡單介紹一下 ASP.NET Core 中的 Session 的使用方法。     安裝配置 Session nuget 新增引用 M

ASP.NET Core 2 學習筆記依賴註入

pub framework 三次 DDM order 包裝 差異 限制 cto 原文:ASP.NET Core 2 學習筆記(四)依賴註入ASP.NET Core使用了大量的依賴註入(Dependency Injection, DI),把控制反轉(Inversion Of

asp.net core ioc 依賴注入

1.生命週期 內建的IOC有三種生命週期: Transient: Transient服務在每次被請求時都會被建立。這種生命週期比較適用於輕量級的無狀態服務。 Scoped: Scoped生命週期的服務是每次web請求被建立。 Singleton: Singleton生命能夠週期服務在第一被請求時建立,在後續

.Net Core依賴注入服務使用總結

一、依賴注入   引入依賴注入的目的是為了解耦和。說白了就是面向介面程式設計,通過呼叫介面的方法,而不直接例項化物件去呼叫。這樣做的好處就是如果添加了另一個種實現類,不需要修改之前程式碼,只需要修改注入的地方將實現類替換。上面的說的通過介面呼叫方法,實際上還是需要去例項化介面的實現類,只不過不需要我們手動n

Asp.Net Core WebAPI入門整理簡單示例

序列 open exc tor pda template ssa net found 一、Core WebAPI中的序列化 使用的是Newtonsoft.Json,自定義全局配置處理: // This method gets called by the runtime.

Asp.Net Core WebAPI入門整理跨域處理

使用 所有 ble 允許 需要 public cors 項目 listitem 一、Core WebAPI中的跨域處理 1.在使用WebAPI項目的時候基本上都會用到跨域處理 2.Core WebAPI的項目中自帶了跨域Cors的處理,不需要單獨添加程序包 3.使用方

asp.net core入門教程系列

home padding 方式 title sys 活性 elf tro ash Asp.Net Core簡介 ASP.NET Core 是一個全新的開源、跨平臺框架,可以用它來構建基於網絡連接的現代雲應用程序,比如:Web 應用,IoT(Internet Of Thin

ASP.NET Core快速入門】準備CentOS和Nginx環境

正常 b- 進入 運行 ins 輸入 最小 我們 -128 基本軟件 VMware虛擬機 centos:http://isoredirect.centos.org/centos/7/isos/x86_64/CentOS-7-x86_64-Minimal-1708.iso

ASP.NET Core快速入門】在CentOS上安裝.NET Core運行時、部署到CentOS

ati libunwind serve code api cal 之前 prompt conn 下載.NET Core SDK 下載地址:https://www.microsoft.com/net/download/windows 第一步:Add the dotne

ASP.NET Core快速入門】 RoutingMiddleware介紹以及MVC引入

pre configure onf mvc tin 常用 esp red 引入 前言 前面我們介紹了使用app.Map來配置路由,但是對於一般不是特別大的項目來說,我們不使用Map來進行路由配置。 配置路由 我們首先需要在Startup.cs文件中的Configu

asp.net core 擁抱 docker 技術 概覽

測試 docker 架構 swa ima 進程 基於 概念 registry 這是一個huge 坑慢慢填吧。這裏只是一個目錄 或總覽。 docker 是什麽? docker可以看做一種虛擬機技術,但沒有傳統虛擬機那麽復雜,是基於進程的虛擬,就是讓一個一個進程,認為自己處於一

ASP.NET Core 2 學習筆記視圖

部分 合成 cati 分享 col script text var AC ASP.NET Core MVC中的Views是負責網頁顯示,將數據一並渲染至UI包含HTML、CSS等。並能痛過Razor語法在*.cshtml中寫渲染畫面的程序邏輯。本篇將介紹ASP.NET Co

ASP.NET Core 2 學習筆記生命周期

RF Go 使用 HR runt block top 最大的 env 原文:ASP.NET Core 2 學習筆記(二)生命周期要了解程序的運行原理,就要先知道程序的進入點及生命周期。以往ASP.NET MVC的啟動方式,是繼承 HttpApplication 作為網站開始

ASP.NET Core 2 學習筆記路由

local quest urn AD term 執行 自動 routes code 原文:ASP.NET Core 2 學習筆記(七)路由ASP.NET Core通過路由(Routing)設定,將定義的URL規則找到相對應行為;當使用者Request的URL滿足特定規則條件

ASP.NET Core 2 學習筆記MVC

方便 web redirect AR return his 架構模式 PE ofo 原文:ASP.NET Core 2 學習筆記(六)MVC ASP.NET Core MVC跟ASP.NET MVC觀念是一致的,使用上也沒有什麽太大的變化。之前的ASP.NET MVC把MV

ASP.NET Core 配置跨域CORS

tin sha har exce pub header service 策略 uil 1.安裝程序CORS程序包 Install-Package Microsoft.AspNetCore.Mvc.Cors 一般默認都帶了此程序包的 2.配置CORS服務 在 Startu

ASP.NET Core快速入門】部署到IIS

圖片 cor .com servers 訪問 publish img 控制臺 -m 原文:【ASP.NET Core快速入門】(二)部署到IIS配置IIS模塊 ASP.NET Core Module載地址:https://docs.microsoft.com/en-us/

Asp.net Core IdentityServer4 入門教程:概念解析

什麽 ica 統一 理解 給他 分享 目錄 .net 系統 目錄 1、IdentityServer4 是什麽 2、什麽是OpenID和OAuth 2.0協議 3、IdentityServer4 可以用來做什麽 其他 1、IdentityServer4 是什麽 Ident

ASP.NET Core 專案配置 ( Startup )轉載

原文:https://www.twle.cn/l/yufei/aspnetcore/dotnet-aspnet-startup.html 由於是個人網站,怕沒了,特意複製儲存,個人覺得講的非常透徹   前面幾章節中我們已經介紹和使用過 Startup 類