1. 程式人生 > >ASP.NET Core中的依賴注入(5): ServiceProvider實現揭祕 【解讀ServiceCallSite 】

ASP.NET Core中的依賴注入(5): ServiceProvider實現揭祕 【解讀ServiceCallSite 】

通過上一篇的介紹我們應該對實現在ServiceProvider的總體設計有了一個大致的瞭解,但是我們刻意迴避一個重要的話題,即服務例項最終究竟是採用何種方式提供出來的。ServiceProvider最終採用何種方式提供我們所需的服務例項取決於最終選擇了怎樣的ServiceCallSite,而服務註冊是採用的ServiceDescriptor有決定了ServiceCallSite型別的選擇。我們將眾多不同型別的ServiceCallSite大體分成兩組,一組用來建立最終的服務例項,另一類則與生命週期的管理有關。

一、用於服務建立的ServiceCallSite

服務例項的建立方式主要有三種,分別對應ServiceDescriptor如下三個只讀屬性。簡單來說,如果ImplementationInstance屬性返回一個具體的物件,該物件將直接作為提供的服務例項。如果屬性ImplementationFactory返回一個具體的委託物件,該委託將會作為提供服務例項的工廠。除此之外,ServiceProvider將會利用ImplementationType屬性返回的真是服務型別定位某一個最佳的建構函式來建立最終提供的服務例項。

   1: public class ServiceDescriptor
   2: {
   3:     public Type                               ImplementationType {  get; }
   4:     public object                             ImplementationInstance {  get; }
   5:     public Func<IServiceProvider, object>     ImplementationFactory {  get; }      
   6: }

服務例項的這三種不同的建立方式最終由三種對應的ServiceCallSite型別來完成,我們將它們的型別分別命名為InstanceCallSite、FactoryCallSite和ConstructorCallSite。如下面的程式碼片段所示,前兩種ServiceCallSite(InstanceCallSite和FactoryCallSite)的實現非常簡單,所以我們在這裡就不對它們多做介紹了。

   1: internal class InstanceCallSite : IServiceCallSite
   2: {
   3: public object Instance { get; private
set; }
   4:  
   5:     public InstanceCallSite(object instance)
   6:     {
   7:         this.Instance = instance;
   8:     }
   9:     public Expression Build(Expression provider)
  10:     {
  11:         return Expression.Constant(this.Instance);
  12:     }
  13:     public object Invoke(ServiceProvider provider)
  14:     {
  15:         return Instance;
  16:     }
  17: }
  18:  
  19: internal class FactoryCallSite : IServiceCallSite
  20: {
  21:     public Func<IServiceProvider, object> Factory { get; private set; }
  22:     public FactoryCallSite(Func<IServiceProvider, object> factory)
  23:     {
  24:         this.Factory = factory;
  25:     }
  26:     public Expression Build(Expression provider)
  27:     {
  28:         Expression<Func<IServiceProvider, object>> factory = p => this.Factory(p);
  29:         return Expression.Invoke(factory, provider);
  30:     }
  31:     public object Invoke(ServiceProvider provider)
  32:     {
  33:         return this.Factory(provider);
  34:     }
  35: }

以執行指定建構函式建立服務例項的ConstructorCallSite稍微複雜一點。如下面的程式碼片段所示,我們在建立一個ConstructorCallSite物件的時候除了指定一個代表建構函式的ConstructorInfo物件之外,還需要指定一組用於初始化對應引數列表的ServiceCallSite。

   1: internal class ConstructorCallSite : IServiceCallSite
   2: {
   3:     public ConstructorInfo ConstructorInfo { get; private set; }
   4:     public IServiceCallSite[] Parameters { get; private set; }
   5:  
   6:     public ConstructorCallSite(ConstructorInfo constructorInfo, IServiceCallSite[] parameters)
   7:     {
   8:         this.ConstructorInfo = constructorInfo;
   9:         this.Parameters = parameters;
  10:     }
  11:  
  12:     public Expression Build(Expression provider)
  13:     {
  14:         ParameterInfo[] parameters = this.ConstructorInfo.GetParameters();
  15:         return Expression.New(this.ConstructorInfo, this.Parameters.Select((p, index) => Expression.Convert(p.Build(provider), 
  16:             parameters[index].ParameterType)).ToArray());
  17:     }
  18:  
  19:     public object Invoke(ServiceProvider provider)
  20:     {
  21:         return this.ConstructorInfo.Invoke(this.Parameters.Select(p => p.Invoke(provider)).ToArray());
  22:     }
  23: }

雖然ConstructorCallSite自身建立服務例項的邏輯很簡單,但是如何建立ConstructorCallSite物件本身相對麻煩一些,因為這涉及到如何選擇一個最終建構函式的問題。我們在上面專門介紹過這個問題,並且總結出選擇建構函式採用的兩條基本的策略:

  • ServiceProvider能夠提供建構函式的所有引數。
  • 目標建構函式的引數型別集合是所有有效建構函式引數型別集合的超級。

我們將ConstructorCallSite的建立定義在Service類的CreateConstructorCallSite方法中,它具有額外兩個輔助方法GetConstructor和GetParameterCallSites,前者用於選擇正確的建構函式,後者則為指定的建構函式建立用於初始化引數的ServiceCallSite列表。

   1: internal class Service : IService
   2: {
   3:     private ConstructorCallSite CreateConstructorCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
   4:     {
   5:         ConstructorInfo constructor = this.GetConstructor(provider, callSiteChain);
   6:         if (null == constructor)
   7:         {
   8:             throw new InvalidOperationException("No avaliable constructor");
   9:         }
  10:         return new ConstructorCallSite(constructor, constructor.GetParameters().Select(p => provider.GetServiceCallSite(p.ParameterType, callSiteChain)).ToArray());                                              
  11: }
  12:  
  13:     private ConstructorInfo GetConstructor(ServiceProvider provider, ISet<Type> callSiteChain)
  14:     {
  15:         ConstructorInfo[] constructors = this.ServiceDescriptor.ImplementationType.GetConstructors()
  16:             .Where(c => (null != this.GetParameterCallSites(c, provider, callSiteChain))).ToArray();
  17:  
  18:         Type[] allParameterTypes = constructors.SelectMany(
  19:             c => c.GetParameters().Select(p => p.ParameterType)).Distinct().ToArray();
  20:  
  21:         return constructors.FirstOrDefault(
  22:             c => new HashSet<Type>(c.GetParameters().Select(p => p.ParameterType)).IsSupersetOf(allParameterTypes));
  23:     }
  24:  
  25:     private IServiceCallSite[] GetParameterCallSites(ConstructorInfo constructor,ServiceProvider provider,ISet<Type> callSiteChain)
  26:     {
  27:         ParameterInfo[] parameters = constructor.GetParameters();
  28:         IServiceCallSite[] serviceCallSites = new IServiceCallSite[parameters.Length];
  29:  
  30:         for (int index = 0; index < serviceCallSites.Length; index++)
  31:         {
  32:             ParameterInfo parameter = parameters[index];
  33:             IServiceCallSite serviceCallSite = provider.GetServiceCallSite(
  34:                 parameter.ParameterType, callSiteChain);
  35:             if (null == serviceCallSite && parameter.HasDefaultValue)
  36:             {
  37:                 serviceCallSite = new InstanceCallSite(parameter.DefaultValue);
  38:             }
  39:             if (null == serviceCallSite)
  40:             {
  41:                 return null;
  42:             }
  43:             serviceCallSites[index] = serviceCallSite;
  44:         }
  45:         return serviceCallSites;
  46:     }
  47:     //其他成員
  48: }


二、用於管理生命週期的ServiceCallSite

服務例項最終採用何種提供方式還與服務註冊時採用的生命週期管理模式有關,三種不同的生命週期管理模式(Transient、Scoped和Singleton)分別對應著三種不同的ServiceCallSite型別,我們分別將其命名為TransienCallSite、ScopedCallSite和SingletonCallSite。

對於TransientCallSite來說,它提供服務例項的方式非常直接,那就是直接建立一個新的物件。在此之外還需要考慮一個關於服務回收的問題,那就是如果服務例項對應的型別實現了IDisposable介面,需要將它新增到ServiceProvider的TransientDisposableServices屬性中。TransientCallSite具有如下的定義,只讀屬性ServiceCallSite表示真正用於建立服務例項的ServiceCallSite。

   1: internal class TransientCallSite : IServiceCallSite
            
           

相關推薦

ASP.NET Core依賴注入5: ServiceProvider實現揭祕 解讀ServiceCallSite

通過上一篇的介紹我們應該對實現在ServiceProvider的總體設計有了一個大致的瞭解,但是我們刻意迴避一個重要的話題,即服務例項最終究竟是採用何種方式提供出來的。ServiceProvider最終採用何種方式提供我們所需的服務例項取決於最終選擇了怎樣的ServiceCallSite,而服務註冊是採用的S

ASP.NET Core依賴注入5: ServiceProvider實現揭祕 總體設計

本系列前面的文章我們主要以程式設計的角度對ASP.NET Core的依賴注入系統進行了詳細的介紹,如果讀者朋友們對這些內容具有深刻的理解,我相信你們已經可以正確是使用這些與依賴注入相關的API了。如果你還對這個依賴注入系統底層的實現原理具有好奇心,可以繼續閱讀這一節的內容。 目錄一、ServiceCall

ASP.NET CORE 使用 SESSION 轉載

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

ASP.NET Core依賴注入5:ServicePrvider實現揭祕補充漏掉的細節

到目前為止,我們定義的ServiceProvider已經實現了基本的服務提供和回收功能,但是依然漏掉了一些必需的細節特性。這些特性包括如何針對IServiceProvider介面提供一個ServiceProvider物件,何建立ServiceScope,以及如何提供一個服務例項的集合。 一、提供一個Serv

asp.net -mvc框架復習5-ASP.NET MVC的視圖簡單使用

font height logs 認識 知識 分類 ges mil c中 1.視圖分類 ASPX視圖(現在講解) Razor視圖(後面講解) ASPX 視圖: 2.@page指令 作用:頁面的聲明 要求:必須放在第一行,常用指令屬性如下: 3.服務器端內嵌

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 擁抱 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 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 類

ASP.NET Core AD 域登入 轉載

在選擇AD登入時,其實可以直接選擇 Windows 授權,不過因為有些網站需要的是LDAP獲取資訊進行授權,而非直接依賴Web Server自帶的Windows 授權功能。   當然如果使用的是Azure AD/企業賬號登入時,直接在ASP.NET Core建立專案時選擇就好了。來個ABC:1.