ASP.NET Core中的依賴註入(3): 服務的註冊與提供

来源:http://www.cnblogs.com/artech/archive/2016/04/06/asp-net-core-di-register.html
-Advertisement-
Play Games

在採用了依賴註入的應用中,我們總是直接利用DI容器直接獲取所需的服務實例,換句話說,DI容器起到了一個服務提供者的角色,它能夠根據我們提供的服務描述信息提供一個可用的服務對象。ASP.NET Core中的DI容器體現為一個實現了IServiceProvider介面的對象。 一、ServiceProv... ...


在採用了依賴註入的應用中,我們總是直接利用DI容器直接獲取所需的服務實例,換句話說,DI容器起到了一個服務提供者的角色,它能夠根據我們提供的服務描述信息提供一個可用的服務對象。ASP.NET Core中的DI容器體現為一個實現了IServiceProvider介面的對象。

一、ServiceProvider與ServiceDescriptor

我一直覺得優秀的設計首先應該是簡單的設計,至少是看起來簡單的設計,這就是我們所謂的大道至簡。作為一個服務的提供者,ASP.NET Core中的DI容器最終體現為一個IServiceProvider介面,我們將所有實現了該介面的類型及其實例統稱為ServiceProvider。如下麵的代碼片段所示,該介面簡單至極,它僅僅提供了唯一個GetService方法,該方法根據提供的服務類型為你提供對應的服務實例。

   1: public interface IServiceProvider
   2: {
   3:     object GetService(Type serviceType);
   4: }

ASP.NET Core內部真正使用的是一個實現了IServiceProvider介面的內部類型(該類型的名稱為“ServiceProvider”),我們不能直接創建該對象,只能間接地通過調用IServiceCollection介面的擴展方法BuildServiceProvider得到它。IServiceCollection介面定義在“Microsoft.Extensions.DependencyInjection”命名空間下,如果沒有特別說明,本系列文章涉及到的與ASP.NET Core依賴註入相關的類型均採用此命名空間。 如下麵的代碼片段所示,IServiceCollection介面實際上代表一個元素為ServiceDescriptor對象的集合,它直接繼承了另一個介面IList<ServiceDescriptor>,而ServiceCollection類實現了該介面。

   1: public static class ServiceCollectionExtensions
   2: {
   3:     public static IServiceProvider BuildServiceProvider(this IServiceCollection services);
   4: }
   5:  
   6: public interface IServiceCollection : IList<ServiceDescriptor>
   7: {}
   8:  
   9: Public class ServiceCollection: IServiceCollection
  10: {
  11:     //省略成員
  12: }

體現為DI容器的ServiceProvider之所以能夠根據我們給定的服務類型(一般是一個介面類型)提供一個能夠開箱即用的服務實例,是因為我們預先註冊了相應的服務描述信息,這些指導ServiceProvider正確實施服務提供操作的服務描述體現為如下一個ServiceDescriptor類型。

   1: public class ServiceDescriptor
   2: {
   3:     public ServiceDescriptor(Type serviceType, object instance);
   4:     public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
   5:     public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
   6:  
   7:     public Type                                ServiceType {  get; }
   8:     public ServiceLifetime                     Lifetime {  get; }
   9:  
  10:     public Type                                ImplementationType {  get; }
  11:     public object                              ImplementationInstance {  get; }
  12:     public Func<IServiceProvider, object>      ImplementationFactory {  get; }      
  13: }

ServiceDescriptor的ServiceType屬性代表提供服務的生命類型,由於標準化的服務一般會定義成介面,所以在絕大部分情況下體現為一個介面類型。類型為ServiceLifetime的屬性Lifetime體現了ServiceProvider針對服務實例生命周期的控制方式。如下麵的代碼片段所示,ServiceLifetime是一個美劇類型,定義其中的三個選項(Singleton、Scoped和Transient)體現三種對服務對象生命周期的控制形式,我們將在本節後續部分對此作專門的介紹。

   1: public enum ServiceLifetime
   2: {
   3:     Singleton,
   4:     Scoped,
   5:     Transient
   6: }

3-10對於ServiceDescriptor的其他三個屬性來說,它們實際上是輔助ServiceProvider完成具體的服務實例提供操。ImplementationType屬性代表被提供服務實例的真實類型,屬性ImplementationInstance則直接代表被提供的服務實例,ImplementationFactory則提供了一個創建服務實例的委托對象。ASP.NET Core與依賴註入相關的幾個核心類型具有如圖10所示的關係。

由於ASP.NET Core中的ServiceProvider是根據一個代表ServiceDescriptor集合的IServiceCollection對象創建的,當我們調用其GetService方法的時候,它會根據我們提供的服務類型找到對應的ServiceDecriptor對象。如果該ServiceDecriptor對象的ImplementationInstance屬性返回一個具體的對象,該對象將直接用作被提供的服務實例。如果ServiceDecriptor對象的ImplementationFactory返回一個具體的委托,該委托對象將直接用作創建服務實例的工廠。

如果這兩個屬性均為Null,ServiceProvider才會根據ImplementationType屬性返回的類型調用相應的構造函數創建被提供的服務實例。至於我們在上面一節中提到的三種依賴註入方式,ServiceProvider僅僅支持構造器註入,屬性註入和方法註入的支持並未提供。

二、服務的註冊與提供

ASP.NET Core針對依賴註入的編程主要體現在兩個方面:其一,創建一個ServiceCollection對象並將服務註冊信息以ServiceDescriptor對象的形式添加其中;其二,針對ServiceCollection對象創建對應的ServiceProvider並利用它提供我們需要的服務實例。

在進行服務註冊的時候,我們可以直接調用相應的構造函數創建ServiceDescriptor對象並將其添加到ServiceCollection對象之中。除此之外,IServiceCollection介面還具有如下三組擴展方法將這兩個步驟合二為一。從下麵給出的代碼片段我們不難看出這三組擴展方法分別針對上面我們提及的三種針對服務實例的生命周期控制方式,泛型參數TService代表服務的聲明類型,即ServiceDescriptor的ServiceType屬性,至於ServiceDescriptor的其他屬性,則通過方法相應的參數來提供。

   1: public static class ServiceCollectionExtensions
   2: {
   3:     public static IServiceCollection AddScoped<TService>(this IServiceCollection services) where TService: class;
   4:    //其他AddScoped<TService>重載
   5:  
   6:     public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService: class;
   7:    //其他AddSingleton<TService>重載
   8:  
   9:     public static IServiceCollection AddTransient<TService>(this IServiceCollection services) where TService: class;
  10:     //其他AddTransient<TService>重載
  11: }

對於用作DI容器的ServiceProvider對象來說,我們可以直接調用它的GetService方法根據指定的服務類型獲得想用的服務實例。除此之外,服務的提供還可以通過IServiceProvider介面相應的構造函數來完成。如下麵的代碼片段所示,擴展方法GetService<T>以泛型參數的形式指定服務的聲明類型。至於另外兩個擴展方法GetRequiredService和GetRequiredService<T>,如果ServiceProvider不能提供一個具體的服務實例,一個InvalidOperationException異常會被拋出來並提示相應的服務註冊信息不足。

   1: public static class ServiceProviderExtensions
   2: { 
   3:     public static T GetService<T>(this IServiceProvider provider);
   4:     public static object GetRequiredService(this IServiceProvider provider, Type serviceType);
   5:     public static T GetRequiredService<T>(this IServiceProvider provider);
   6: }

實例演示:利用ServiceProvider來提供服務

接下來採用實例演示的方式來介紹如何利用ServiceCollection進行服務註冊,以及如何利用ServiceCollection創建對應的ServiceProvider來提供我們需要的服務實例。我們在一個控制台應用中定義四個服務介面(IFoo、IBar、IBaz和IGux)以及分別實現它們的四個服務類(Foo、Bar、Baz和Gux)如下麵的代碼片段所示,IGux具有三個只讀屬性(Foo、Bar和Baz)均為介面類型,併在構造函數中進行初始化。

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

現在我們在作為程式入口的Main方法中創建了一個ServiceCollection對象,並採用不同的方式完成了針對四個服務介面的註冊。具體來說,對於正對服務介面IFoo和IGux的ServiceDescriptor來說,我們指定了代表服務真實類型的ImplementationType屬性,而對於針對服務介面IBar和IBaz的ServiceDescriptor來說,我們初始化的則是分別代表服務實例和服務工廠的ImplementationInstance個ImplementationFactory屬性。由於我們調用的是AddSingleton方法,所以四個ServiceDescriptor的Lifetime屬性均為Singleton。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         IServiceCollection services = new ServiceCollection()
   6:             .AddSingleton<IFoo, Foo>()
   7:             .AddSingleton<IBar>(new Bar())
   8:             .AddSingleton<IBaz>(_ => new Baz())
   9:             .AddSingleton<IGux, Gux>();
  10:  
  11:         IServiceProvider serviceProvider = services.BuildServiceProvider();
  12:         Console.WriteLine("serviceProvider.GetService<IFoo>(): {0}",serviceProvider.GetService<IFoo>());
  13:         Console.WriteLine("serviceProvider.GetService<IBar>(): {0}", serviceProvider.GetService<IBar>());
  14:         Console.WriteLine("serviceProvider.GetService<IBaz>(): {0}", serviceProvider.GetService<IBaz>());
  15:         Console.WriteLine("serviceProvider.GetService<IGux>(): {0}", serviceProvider.GetService<IGux>());
  16:     }
  17: }

接下來我們調用ServiceCollection對象的擴展方法BuildServiceProvider得到對應的ServiceProvider對象,然後調用其擴展方法GetService<T>分別獲得針對四個介面的服務實例對象並將類型名稱其輸出到控制臺上。運行該程式之後,我們會在控制臺上得到如下的輸出結果,由此印證ServiceProvider為我們提供了我們期望的服務實例。

   1: serviceProvider.GetService<IFoo>(): Foo
	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在做3D漫游時,分別運用WASD和方向鍵,控制視角前後左右,KeyDown事件記錄漫游開始標記,但是WASD可以Debug進入,方向鍵卻始終無法進入,很奇怪的是,進不了KeyDown,卻能響應KeyUp事件 但是Ctrl+方向鍵和Alt+方向鍵卻可以進入,經過一番查找。很多文章中分析可能是因為方向鍵 ...
  • 一、前言 DependencyResolver是MVC中一個重要的組件,從名字可以看出,它負責依賴對象的解析,可以說它是MVC框架內部使用的一個IOC容 器。MVC內部很多對象的創建都是通過它完成的,或許我們平時沒有直接用到它,但是如果你在使用unity、autofac,或者在看一些開源項目時,總 ...
  • 創建二種方式 ...
  • 前言 建議13、為類型輸出格式化字元串 建議14、正確實現淺拷貝和深拷貝 建議15、使用dynamic來簡化反射實現 建議13、為類型輸出格式化字元串 有兩種方法可以為類型提供格式化的字元串輸出。 一種是意識到類型會產生格式化字元串輸出,於是讓類型繼承介面IFormattable。這對類型來說,是一 ...
  • JQuery UI 是以 JQuery 為基礎的開源 JavaScript 網頁用戶界面代碼庫。包含底層用戶交互、動畫、特效和可更換主題的可視控制項,這些控制項主要包括:Accordion,Autocomplete,ColorPicker,Dialog,Slider,Tabs,DatePicker,Ma ...
  • 查找了msdn上關於bool的介紹,整理如下: bool 關鍵字是 System.Boolean 的別名。它用於聲明變數來存儲布爾值 true 和 false。 如果需要一個也可以有 null 值的布爾型變數,請使用 bool?。 bool 變數的預設值為 false。bool? 變數的預設值為 n ...
  • ...
  • ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...