学习ASP.NET Core,你必须了解无处不在的“依赖注入”
ASP.NET Core的核心是通過一個(gè)Server和若干注冊(cè)的Middleware構(gòu)成的管道,不論是管道自身的構(gòu)建,還是Server和Middleware自身的實(shí)現(xiàn),以及構(gòu)建在這個(gè)管道的應(yīng)用,都需要相應(yīng)的服務(wù)提供支持,ASP.NET Core自身提供了一個(gè)DI容器來實(shí)現(xiàn)針對(duì)服務(wù)的注冊(cè)和消費(fèi)。換句話說,不只是ASP.NET Core底層框架使用的服務(wù)是由這個(gè)DI容器來注冊(cè)和提供,應(yīng)用級(jí)別的服務(wù)的注冊(cè)和提供也需要以來這個(gè)DI容器,所以正如本文標(biāo)題所說的——學(xué)習(xí)ASP.NET Core,你必須了解無處不在的“依賴注入”。
目錄一、依賴注入簡(jiǎn)介
二、依賴注入在管道構(gòu)建過程中的應(yīng)用
三、依賴服務(wù)的注冊(cè)與注入
四、讓Startup的ConfigureServices方法返回一個(gè)ServiceProvider
五、ASP.NET Core默認(rèn)注冊(cè)了哪些服務(wù)
六、ASP.NET Core MVC中的依賴注入
一、依賴注入簡(jiǎn)介
說到依賴注入(Dependency Injection,以下簡(jiǎn)稱DI),就必須說IoC(Inverse of Control),很多人將這兩這混為一談,其實(shí)這是兩個(gè)完全不同的概念,或者是不同“層次”的兩個(gè)概念,我曾在《控制反轉(zhuǎn)(IoC)》和《依賴注入(DI)》對(duì)這兩個(gè)概念做過詳細(xì)介紹。ASP.NET Core使用的DI框架由“Micorosoft.Extensions.DependencyInjection”這個(gè)NuGet包來承載,我們也可以非ASP.NET Core應(yīng)用或者你自己的框架上單獨(dú)使用它,對(duì)于這個(gè)DI框架的設(shè)計(jì)、實(shí)現(xiàn)以及編程相關(guān)的內(nèi)容,我在系列文章《ASP.NET Core 中的依賴注入 [共7篇]》對(duì)此有過詳細(xì)的介紹。
DI框架具有兩個(gè)核心的功能,即服務(wù)的注冊(cè)和提供,這兩個(gè)功能分別由對(duì)應(yīng)的對(duì)象來承載, 它們分別是ServiceCollection和ServiceProvider。如下圖所示,我們將相應(yīng)的服務(wù)以不同的生命周期模式(Transient、Scoped和Singleton)注冊(cè)到ServiceCollection對(duì)象之上,在利用后者創(chuàng)建的ServiceProvider根據(jù)注冊(cè)的服務(wù)類型提取相應(yīng)的服務(wù)對(duì)象。
二、依賴注入在管道構(gòu)建過程中的使用
在ASP.NET Core管道的構(gòu)架過程中主要涉及三個(gè)對(duì)象/類型,作為宿主的WebHost和他的創(chuàng)建者WebHostBuilder,以及注冊(cè)到WebHostBuilder的Startup類型。 如下的代碼片段體現(xiàn)了啟動(dòng)ASP.NET Core應(yīng)用采用的典型編程模式:我們首先創(chuàng)建一個(gè)WebHostBuilder對(duì)象,并將采用Server和Startup類型注冊(cè)到它之上。在調(diào)用Build方法創(chuàng)建WebHost之前,我們還可以調(diào)用相應(yīng)的方式做其他所需的注冊(cè)工作。當(dāng)我們調(diào)用WebHost的Run方法之后,后者會(huì)利用注冊(cè)的Startup類型來構(gòu)建完整的管道。那么在管道的構(gòu)建過程中,DI是如何被應(yīng)用的呢?
? ?1: new WebHostBuilder()
? ?2: ? ? .UseKestrel()
? ?3: ? ? .UseStartup<Startup>()
? ?4: ? ? .Xxx
? ?5: ? ? .Build()
? ?6: ? ? .Run()
DI在管道ASP.NET Core管道構(gòu)建過程中的應(yīng)用基本體現(xiàn)下面這個(gè)序列圖中。當(dāng)我們調(diào)用WebHostBuilder的Build方法創(chuàng)建對(duì)應(yīng)的WebHost的時(shí)候,前者會(huì)創(chuàng)建一個(gè)ServiceCollection對(duì)象,并將一系列預(yù)定義的服務(wù)注冊(cè)在它之上。接下來WebHostBuilder會(huì)利用這個(gè)ServiceCollection對(duì)象創(chuàng)建出對(duì)應(yīng)的ServieProvider,這個(gè)ServiceProvider和ServiceCollection對(duì)象會(huì)一并傳遞給最終創(chuàng)建WebHost對(duì)象。當(dāng)我們調(diào)用WebHost的Run方法啟動(dòng)它的時(shí)候,如果注冊(cè)的Startup是一個(gè)實(shí)例類型,它會(huì)利用這個(gè)ServiceProvider以構(gòu)造器注入的方式創(chuàng)建對(duì)應(yīng)的Startup對(duì)象。說的具體一點(diǎn),我們注冊(cè)的Startup類型的構(gòu)造函數(shù)是允許定義參數(shù)的,但是參數(shù)類型必須是預(yù)先注冊(cè)到ServiceCollection中的服務(wù)類型。
注冊(cè)的Startup方法可以包含一個(gè)可選的ConfigureServices方法,這個(gè)方法具有一個(gè)類型為IServiceCollection接口的參數(shù)。WebHost會(huì)將WebHostBuilder傳遞給它的ServiceCollection作為參數(shù)調(diào)用這個(gè)ConfigureServices方法,而我們則利用這個(gè)方法將注冊(cè)的中間件和應(yīng)用所需的服務(wù)注冊(cè)到這個(gè)ServiceCollection對(duì)象上。在這之后,所有需要的服務(wù)(包括框架和應(yīng)用注冊(cè)的服務(wù))都注冊(cè)到這個(gè)ServiceCollection上面,WebHost會(huì)利用它創(chuàng)建一個(gè)新的ServiceProvider。WebHost會(huì)利用這個(gè)ServiceProvider對(duì)象以方法注入的方式調(diào)用Startup對(duì)象/類型的Configure方法,最終完成你對(duì)整個(gè)管道的建立。換句話會(huì)說,定義在Startup類型中旨在用于注冊(cè)Middleware的Configure方法除了采用IApplicationBuilder作為第一個(gè)參數(shù)之外,它依然可以采用注冊(cè)的任何一個(gè)服務(wù)類型作為后續(xù)參數(shù)的類型。
服務(wù)的注冊(cè)除了是現(xiàn)在注冊(cè)的Startup類型的ConfigureServices方法之外,實(shí)際上還具有另一個(gè)實(shí)現(xiàn)方式,那就是調(diào)用WebHostBuilder具有如下定義的ConfigureServices方法。當(dāng)WebHostBuilder創(chuàng)建出ServiceCollection對(duì)象并完成了默認(rèn)服務(wù)的注冊(cè)后,我們通過調(diào)用這個(gè)方法所傳入的所有Action<IServiceCollection>對(duì)象將最終應(yīng)用到這個(gè)ServiceCollection對(duì)象上。
? ?1: public interface IWebHostBuilder
? ?2: {
? ?3: ? ? IWebHostBuilder ConfigureServiecs(Action<IServiceCollection> configureServices);
? ?4: }
值得一提的是,Startup類型的ConfigureServices方法是允許具有一個(gè)IServiceProvider類型的返回值,如果這個(gè)方法返回一個(gè)具體的ServiceProrivder,那么WebHost將不會(huì)利用ServiceCollection來創(chuàng)建ServiceProvider,而是直接使用這個(gè)返回的ServiceProvider來調(diào)用Startup對(duì)象/類型的Configure方法。這實(shí)際上是一個(gè)很有用的擴(kuò)展點(diǎn),我們使用它可以實(shí)現(xiàn)針對(duì)其它DI框架的集成。
三、依賴服務(wù)的注冊(cè)與注入
接下來我們通過一個(gè)實(shí)例來演示如何利用Startup類型的ConfigureServices來注冊(cè)服務(wù),以及發(fā)生在Startup類型上的兩種依賴注入形式。如下面的代碼片段所示,我們定義了兩個(gè)服務(wù)接口(IFoo和IBar)和對(duì)應(yīng)的實(shí)現(xiàn)類型(Foo和Bar)。其中其中服務(wù)Foo是通過調(diào)用WebHostBuilder的ConfigureServices方法進(jìn)行注冊(cè)的,而另一個(gè)服務(wù)Bar的注冊(cè)則發(fā)生在Startup的ConfigureServices方法上。對(duì)于Startup來說,它具有一個(gè)類型為IFoo的只讀屬性,該屬性在構(gòu)造函數(shù)利用傳入的參數(shù)進(jìn)行初始化,不用是這體現(xiàn)了針對(duì)Startup的構(gòu)造器注入。Startup的Configure方法除了ApplicationBuilder作為第一個(gè)參數(shù)之外,還具有另一個(gè)類型為IBar的參數(shù),我們利用它來演示方法注入。
? ?1: public interface IFoo { }
? ?2: public interface IBar { }
? ?3: public class Foo : IFoo { }
? ?4: public class Bar : IBar { }
? ?5: ?
? ?6: public class Program
? ?7: {
? ?8: ? ? public static void Main(string[] args)
? ?9: ? ? {
? 10: ? ? ? ? new WebHostBuilder()
? 11: ? ? ? ? ? ? .ConfigureServices(services=>services.AddSingleton<IFoo, Foo>())
? 12: ? ? ? ? ? ? .UseKestrel()
? 13: ? ? ? ? ? ? .UseStartup<Startup>()
? 14: ? ? ? ? ? ? .Build()
? 15: ? ? ? ? ? ? .Run();
? 16: ? ? }
? 17: }
? 18: public class Startup
? 19: {
? 20: ? ? public IFoo Foo { get; private set; }
? 21: ? ? public Startup(IFoo foo)
? 22: ? ? {
? 23: ? ? ? ? this.Foo = foo;
? 24: ? ? } ? ?
? 25: ? ? public void ConfigureServices(IServiceCollection services)
? 26: ? ? {
? 27: ? ? ? ? services.AddTransient<IBar, Bar>();
? 28: ? ? }
? 29: ? ??
? 30: ? ? public void Configure(IApplicationBuilder app, IBar bar)
? 31: ? ? {
? 32: ? ? ? ? app.Run(async context =>
? 33: ? ? ? ? {
? 34: ? ? ? ? ? ? context.Response.ContentType = "text/html";
? 35: ? ? ? ? ? ? await context.Response.WriteAsync($"IFoo=>{this.Foo}<br/>");
? 36: ? ? ? ? ? ? await context.Response.WriteAsync($"IBar=>{bar}");
? 37: ? ? ? ? });
? 38: ? ? }
? 39: }
在Startup的Configure方法中,我們調(diào)用ApplicationBulder的Run方法注冊(cè)了一個(gè)Middleware,后者將兩個(gè)注入的服務(wù)的類型作為響應(yīng)的內(nèi)容。當(dāng)我們運(yùn)行這個(gè)應(yīng)用,并利用瀏覽器訪問默認(rèn)的監(jiān)聽地址(http://localhost:5000)時(shí),瀏覽器會(huì)將注入的兩個(gè)服務(wù)對(duì)象的類型以下圖的方式展現(xiàn)出來。
四、讓Startup的ConfigureServices方法返回一個(gè)ServiceProvider
我們說注冊(cè)的Startup類型的ConfigureServices允許返回一個(gè)ServiceProvider,這個(gè)特性的重要意義在于它使我們可以實(shí)現(xiàn)與第三方DI框架(比如Unity、Castle、Ninject和AutoFac等)的集成。我們照例采用一個(gè)實(shí)例對(duì)此做一個(gè)演示,簡(jiǎn)單起見,我們并不會(huì)真正利用某個(gè)具體的DI框架來創(chuàng)建這個(gè)ServiceProvider,而是直接創(chuàng)建一個(gè)新的ServiceCollection來創(chuàng)建它,為此我們對(duì)上面這個(gè)程序進(jìn)行了如下的改寫。
? ?1: public class Program
? ?2: {
? ?3: ? ? public static void Main(string[] args)
? ?4: ? ? {
? ?5: ? ? ? ? new WebHostBuilder()
? ?6: ? ? ? ? ? ? .UseKestrel()
? ?7: ? ? ? ? ? ? .UseStartup<Startup>()
? ?8: ? ? ? ? ? ? .Build()
? ?9: ? ? ? ? ? ? .Run();
? 10: ? ? }
? 11: }
? 12: public class Startup
? 13: { ??
? 14: ? ? public IServiceProvider ConfigureServices(IServiceCollection services)
? 15: ? ? {
? 16: ? ? ? ? IServiceCollection newServices = new ServiceCollection();
? 17: ? ? ? ? foreach (ServiceDescriptor service in services)
? 18: ? ? ? ? {
? 19: ? ? ? ? ? ? newServices.Add(service);
? 20: ? ? ? ? }
? 21: ?
? 22: ? ? ? ? return newServices
? 23: ? ? ? ? ? ? .AddSingleton<IFoo, Foo>()
? 24: ? ? ? ? ? ? .AddSingleton<IBar, Bar>()
? 25: ? ? ? ? ? ? .BuildServiceProvider();
? 26: ? ? }
? 27: ? ??
? 28: ? ? public void Configure(IApplicationBuilder app, IFoo foo, IBar bar)
? 29: ? ? {
? 30: ? ? ? ? app.Run(async context =>
? 31: ? ? ? ? {
? 32: ? ? ? ? ? ? context.Response.ContentType = "text/html";
? 33: ? ? ? ? ? ? await context.Response.WriteAsync($"IFoo=>{foo}<br/>");
? 34: ? ? ? ? ? ? await context.Response.WriteAsync($"IBar=>{bar}");
? 35: ? ? ? ? });
? 36: ? ? }
? 37: }
如上面的代碼片段所示,在Startup的ConfigureServices方法中,我們通過拷貝注冊(cè)到現(xiàn)有ServiceCollection的所有ServiceDescriptor生成了一個(gè)新的ServiceCollection,兩個(gè)服務(wù)Foo和Bar被注冊(cè)到后者之上。該方法最終返回由這個(gè)新ServiceCollection創(chuàng)建的ServiceProvider。在另一個(gè)Configure方法中,我們添加了兩個(gè)類型分別為IFoo和IBar的參數(shù),并以相同的方式將它們的真實(shí)類型名稱和注冊(cè)服務(wù)類型的映射關(guān)系作為響應(yīng)內(nèi)容。程序運(yùn)行之后,我們利用瀏覽器進(jìn)行訪問照樣會(huì)得到一樣的結(jié)果。
五、ASP.NET Core默認(rèn)注冊(cè)了哪些服務(wù)
WebHostBuilder在創(chuàng)建ServiceCollection之后,會(huì)注冊(cè)一些默認(rèn)的服務(wù)。這些服務(wù)和我們自行注冊(cè)的服務(wù)并沒有任何區(qū)別,只要我們知道對(duì)應(yīng)的服務(wù)類型,就可以通過注入的方式獲取并使用它們。那么具體由哪些服務(wù)被默認(rèn)注冊(cè)了呢?如下所示的是這些服務(wù)對(duì)應(yīng)的類型,至于這些服務(wù)各自有何用途,我們?cè)谶@里就先不深究了。
IHostingEnvironment
ILoggerFactory
ILogger<>
IApplicationBuilderFactory
IHttpContextFactory
IOptions<>
DiagnosticSource
DiagnosticListener
IStartupFilter
ObjectPoolProvider
IStartup
如果我們需要這些預(yù)注冊(cè)的服務(wù),我們可以按照我們熟悉的方式以依賴注入的方式來使用它們。如下面的代碼片段所示,我們?cè)赟tartup的Configure方法中直接采用方法注入的方式來使用這些預(yù)定義的服務(wù)。
? ?1: public class Program
? ?2: {
? ?3: ? ? public static void Main(string[] args)
? ?4: ? ? {
? ?5: ? ? ? ? new WebHostBuilder()
? ?6: ? ? ? ? ? ? .UseKestrel()
? ?7: ? ? ? ? ? ? .UseStartup<Startup>()
? ?8: ? ? ? ? ? ? .Build()
? ?9: ? ? ? ? ? ? .Run();
? 10: ? ? }
? 11: }
? 12: public class Startup
? 13: { ? ??
? 14: ? ? public void Configure(
? 15: ? ? ? ? IApplicationBuilder app,
? 16: ? ? ? ? IHostingEnvironment environment,
? 17: ? ? ? ? ILoggerFactory loggerFactory,
? 18: ? ? ? ? IHttpContextFactory httpContextFactory,
? 19: ? ? ? ? DiagnosticSource diagnosticSource,
? 20: ? ? ? ? DiagnosticListener diagnosticListener)
? 21: ? ? {
? 22: ? ? ? ? app.Run(async context =>
? 23: ? ? ? ? {
? 24: ? ? ? ? ? ? context.Response.ContentType = "text/html";
? 25: ? ? ? ? ? ? await context.Response.WriteAsync($"IApplicationBuilder=>{app}<br/>");
? 26: ? ? ? ? ? ? await context.Response.WriteAsync($"IHostingEnvironment=>{environment}<br/>");
? 27: ? ? ? ? ? ? await context.Response.WriteAsync($"ILoggerFactory=>{loggerFactory}<br/>");
? 28: ? ? ? ? ? ? await context.Response.WriteAsync($"IHttpContextFactory=>{httpContextFactory}<br/>");
? 29: ? ? ? ? ? ? await context.Response.WriteAsync($"DiagnosticSource=>{diagnosticSource}<br/>");
? 30: ? ? ? ? ? ? await context.Response.WriteAsync($"DiagnosticListener=>{diagnosticListener}");
? 31: ? ? ? ? });
? 32: ? ? }
? 33: }
由于Configure方法注冊(cè)的Middleware直接將注入服務(wù)的注冊(cè)類型和真實(shí)類型的映射關(guān)系作為響應(yīng)內(nèi)容,所以我們?cè)L問應(yīng)用會(huì)的得到如下所示的輸出結(jié)果。
六、ASP.NET Core MVC中的依賴注入
對(duì)于ASP.NET MVC 5機(jī)器以及之前的版本,在默認(rèn)情況下定義的Controller都具有一個(gè)要求,那就是Controller類型必須具有一個(gè)無參數(shù)的默認(rèn)構(gòu)造函數(shù),否則Controller實(shí)例將無法激活。對(duì)于自身具有依賴注入功能的ASP.NET Core MVC來說,定義Controller將沒有了這個(gè)限制。對(duì)于預(yù)注冊(cè)的服務(wù),我們完全可以采用構(gòu)造器注入的方式在定義的Controller中使用它們。作為演示,我們對(duì)上面這個(gè)應(yīng)用作了如下的改寫。
? ?1: public class Program
? ?2: {
? ?3: ? ? public static void Main(string[] args)
? ?4: ? ? {
? ?5: ? ? ? ? new WebHostBuilder()
? ?6: ? ? ? ? ? ? .UseKestrel()
? ?7: ? ? ? ? ? ? .ConfigureServices(services=>services
? ?8: ? ? ? ? ? ? ? ? .AddSingleton<IFoo,Foo>()
? ?9: ? ? ? ? ? ? ? ? .AddSingleton<IBar,Bar>()
? 10: ? ? ? ? ? ? ? ? .AddMvc())
? 11: ? ? ? ? ? ? .Configure(app=>app.UseMvc())
? 12: ? ? ? ? ? ? .Build()
? 13: ? ? ? ? ? ? .Run();
? 14: ? ? }
? 15: }
? 16: ?
? 17: public class HomeController
? 18: {
? 19: ? ? public IFoo Foo { get; private set; }
? 20: ? ? public IBar Bar { get; private set; }
? 21: ?
? 22: ? ? public HomeController(IFoo foo, IBar bar)
? 23: ? ? {
? 24: ? ? ? ? this.Foo = foo;
? 25: ? ? ? ? this.Bar = bar;
? 26: ? ? }
? 27: ?
? 28: ? ? [HttpGet("/")]
? 29: ? ? public string Index()
? 30: ? ? {
? 31: ? ? ? ? this.HttpContext.Response.ContentType = "text/html";
? 32: ? ? ? ? return $"IFoo=>{this.Foo}<br/>IBar=>{this.Bar}";
? 33: ? ? } ? ? ??
? 34: }
上面這個(gè)代碼與之前有一個(gè)顯著的區(qū)別,那就是我們根本就沒有定義Startup類型,我們將原本實(shí)現(xiàn)在它的兩個(gè)方法(ConfigureServices和Configure)中的功能移植到了WebHostBuilder的同名方法中,這兩種形式的編程方式其實(shí)是等效的。在調(diào)用ConfigureServices方法的時(shí)候,我們除了注冊(cè)MVC相關(guān)的服務(wù)之外,Foo和Bar這兩個(gè)服務(wù)也一并進(jìn)行了注冊(cè)。至于另一個(gè)Configure方法,我們直接調(diào)用其擴(kuò)展方法MVC注冊(cè)與MVC相關(guān)的Middleware。
我們定義了一個(gè)默認(rèn)的HomeController,它具有兩個(gè)類型分別為IFoo和IBar的只讀屬性,后者在構(gòu)造函數(shù)由傳入的參數(shù)進(jìn)行初始化,我們知道這是構(gòu)造器注入的編程方式。在Action方法Index中 ,我們依然將這兩個(gè)服務(wù)的注冊(cè)類型和真實(shí)類型之間的匹配關(guān)系作為響應(yīng)內(nèi)容,所以我們?cè)L問這個(gè)應(yīng)用依然會(huì)得到如下所示的輸出結(jié)果。
請(qǐng)掃描此二維碼或者搜索“大內(nèi)老A”關(guān)注蔣金楠(Artech)微信公眾帳號(hào),你將會(huì)得到及時(shí)的高質(zhì)量技術(shù)文章推送信息。
內(nèi)容轉(zhuǎn)載自公眾號(hào)
大內(nèi)老A 了解更多 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的学习ASP.NET Core,你必须了解无处不在的“依赖注入”的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Asp.Net Core 发布和部署(L
- 下一篇: .NET Core应用类型(Portab