ASP.NET Core 6框架揭秘實例演示[23]:ASP.NET Core應用承載方式的變遷
- 2022 年 3 月 17 日
- 筆記
- .NET 6, .NET Core, [02] 編程技巧, Asp.Net, Asp.Net Core, ASP.NET Core 6框架揭秘
ASP.NET Core應用本質上就是一個由中間件構成的管道,承載系統將應用承載於一個託管進程中運行起來,其核心任務就是將這個管道構建起來。從設計模式的角度來講,「管道」是構建者(Builder)模式最典型的應用場景,所以ASP.NET Core先後採用的三種承載方式都是採用這種模式。(本篇提供的實例已經匯總到《ASP.NET Core 6框架揭秘-實例演示版》)
[S1501]基於IWebHost/IWebHostBuilder的應用承載方式(源程式碼)
[S1502]將初始化設置定義在Startup類型中(源程式碼)
[S1503]基於IHost/IHostBuilder的應用承載方式(源程式碼)
[S1504]將初始化設置定義在Startup類型中(源程式碼)
[S1501]基於IWebHost/IWebHostBuilder的應用承載方式
ASP.NET Core Core 1.X/2.X採用的承載模型以IWebHostBuilder和IWebHost為核心。IWebHost對象代表承載Web應用的宿主(Host),管道隨著IWebHost對象的啟動被構建出來。IWebHostBuilder對象作為宿主對象的構建者,我們針對管道構建的設置都應用在它上面。
這種「原始」的應用承載方式依然被保留了下來,如下這個Hello World應用就是採用的這種承載方式。如程式碼片段所示,我們先創建一個實現了IWebHostBuilder介面的WebHostBuilder對象,並調用其UseKestrel擴展方法註冊了一個Kestrel伺服器。我們接下來調用它的Configure方法利用提供的Action<IApplicationBuilder>委託註冊了一個中間件,該中間件將指定的「Hello World」文本作為響應內容。我們調用IWebHostBuilder對象的Build方法將作為宿主的IWebHost對象構建出來後,我們調用其Run擴展方法將它啟動起來。此時同構註冊的伺服器和中間件組成的管道被構建起來,伺服器開始監聽、接收請求,在將請求交付給後續的中間件進行處理後,它會將響應回復給客戶端。
new WebHostBuilder() .UseKestrel() .Configure(app => app.Run(context => context.Response.WriteAsync("Hello World!"))) .Build() .Run();
按照「面向介面編程」的原則,其實我們不應該調用構造函數去創建一個「空」的WebHostBuilder對象並自行完成針對它的所有設置,而是選擇按照如下的方式調用定義在靜態類型WebHost中的工廠方法CreateDefaultBuilder去創建一個具有默認設置的IWebHostBuilder對象。由於Kestrel伺服器的配置就屬於「默認設置」的一部分,針對UseKestrel擴展方法的調用也不再需要。
using Microsoft.AspNetCore; WebHost.CreateDefaultBuilder() .Configure(app => app.Run(context => context.Response.WriteAsync("Hello World!"))) .Build() .Run();
[S1502]將初始化設置定義在Startup類型中
如果管道涉及過多的 中間件需要註冊,我們還可以將「中間件註冊」這部分工作實現在一個按照約定定義的Startup類型中。由於ASP.NET Core建立在依賴注入框架之上,所以應用往往需要涉及到很多服務註冊,我們一般也會將「服務註冊」的工作也放在這個Startup類型中。我們最終只需要按照如下的方式將這個Startup註冊到創建的IWebHostBuilder對象上就可以了。
using Microsoft.AspNetCore; WebHost.CreateDefaultBuilder() .UseStartup<Startup>() .Build() .Run(); public class Startup { public void ConfigureServices(IServiceCollection services)=> services.AddSingleton<IGreeter, Greeter>(); public void Configure(IApplicationBuilder app, IGreeter greeter)=> app.Run(context => context.Response.WriteAsync(greeter.Greet())); } public interface IGreeter { string Greet(); } public class Greeter : IGreeter { public string Greet() => "Hello World!"; }
[S1503]基於IHost/IHostBuilder的應用承載方式
除了承載Web應用,我們還有很多針對後台服務(比如很多批處理任務)的承載需求,為此微軟推出了以IHostBuilder/IHost為核心的服務承載系統。Web應用本身實際上就是一個長時間運行的後台服務,我們完全可以將應用定義成一個IHostedService服務(GenericWebHostService)。如果將上面介紹的稱為第一代應用承載模式的話,這就是第二代承載模式。IHostBuilder介面定義的很多方法(其中很多是擴展方法)旨在完成兩個方面的設置:第一,為創建的IHost對象及承載的IHostedService服務註冊依賴服務;第二,為服務承載和應用提供相應的配置。如果採用基於IWebHostBuilder/IWebHost的承載方式,上述這兩方面的設置由IWebHostBuilder對象來完成,後者在此基礎上還提供了針對中間件的註冊。
雖然IWebHostBuilder介面提供的除中間件註冊的其他設置基本可以調用IHostBuilder介面相應的方法來完成,但是由於IWebHostBuilder承載的很多配置都是以擴展方法的形式提供的,所以有必要提供針對IWebHostBuilder介面的兼容。基於IHostBuilder/IHost的承載系統中提供對IWebHostBuilder介面的兼容是通過如下所示的ConfigureWebHost擴展方法達成的,GenericWebHostService承載服務也是在這個方法中被註冊的。ConfigureWebHostDefaults擴展方法則會在此基礎上做一些默認設置(如KestrelServer)。
public static class GenericHostWebHostBuilderExtensions { public static IHostBuilder ConfigureWebHost(this IHostBuilder builder,Action<IWebHostBuilder> configure); public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureWebHostBuilder); public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure) }
如果採用基於IHostBuilder/IHost的承載方式,上面演示的「Hello World」應用可以改寫成如下的形式。如程式碼片段所示,在調用Host的靜態工廠方法CreateDefaultBuilder創建出具有默認設置的IHostBuilder對象之後,我們調用它的ConfigureWebHostDefaults擴展方法針對承載ASP.NET Core應用的GenericWebHostService做進一步設置。該方法提供的Action<IApplicationBuilder>委託完成了針對Startup類型的註冊(S1503)。
Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder.UseStartup<Startup>()) .Build() .Run(); public class Startup { public void ConfigureServices(IServiceCollection services) => services.AddSingleton<IGreeter, Greeter>(); public void Configure(IApplicationBuilder app, IGreeter greeter)=> app.Run(context => context.Response.WriteAsync(greeter.Greet())); } public interface IGreeter { string Greet(); } public class Greeter : IGreeter { public string Greet() => "Hello World!"; }
[S1504]將初始化設置定義在Startup類型中
「天下大勢,分久必合,合久必分」,ASP.NET Core應用通過GenericWebHostService這個承載服務被整合到基於IHostBuilder/IHost的服務承載系統中之後,也許微軟還是意識到Web應用和後台服務的承載方式還是應該加以區分,而且它們採用的SDK都不一樣(ASP.NET Core應用採用的SDK為「Microsoft.NET.Sdk.Web」,後台服務採用的SDK一般為「Microsoft.NET.Sdk.Worker」),於是推出了基於WebApplicationBuilder/WebApplication的承載方式。但這一次並非又回到了起點,因為底層的承載方式其實沒有改變,它只是在上面再封裝了一層而已。
新的應用承載方式依然採用「構建者(Builder)」模式,核心的兩個對象分別為WebApplication和WebApplicationBuilder,代表承載應用的WebApplication對象由WebApplicationBuilder對象進行構建。我們可以將其稱為第三代承載模式,它有一個官方的名稱叫做「Minimal API」。第二代承載模式需要提供針對IWebHostBuilder介面的兼容,作為第三代承載模式的Minimal API則需要同時提供針對IWebHostBuilder和IHostBuilder介面的兼容,此兼容性是通過這兩個介面的實現類型ConfigureWebHostBuilder和ConfigureHostBuilder達成的。
WebApplicationBuilder類型的WebHost和Host屬性返回了這兩個對象,之前定義在IWebHostBuilder和IHostBuilder介面上的絕大部分API(並非所有API)藉助它們得以復用。也正是有了這段歷史,我們會發現相同的功能具有兩到三種不同的編程方式。比如IWebHostBuilder和IHostBuilder介面上都提供了註冊服務的方法,而WebApplicationBuilder類型利用Services屬性直接將存放服務註冊的IServiceCollection對象暴露出來,所以任何的服務註冊都可以利用這個屬性來完成。
public sealed class WebApplicationBuilder { public ConfigureWebHostBuilder WebHost { get; } public ConfigureHostBuilder Host { get; } public IServiceCollection Services { get; } public ConfigurationManager Configuration { get; } public ILoggingBuilder Logging { get; } public IWebHostEnvironment Environment { get; } public WebApplication Build(); } public sealed class ConfigureWebHostBuilder : IWebHostBuilder, ISupportsStartup public sealed class ConfigureHostBuilder : IHostBuilder, ISupportsConfigureWebHost
IWebHostBuilder和IHostBuilder介面都提供了設置配置和日誌的方法,這兩方面的設置都可以利用WebApplicationBuilder利用Configuration和Logging暴露出來的ConfigurationManager和ILoggingBuilder對象來實現。既然我們採用了Minimal API,那麼我們就應該儘可能得使用WebApplicationBuilder類型提供得API。如果採用這種全新的承載方式,我們演示的Hello World程式可以改寫成如下的形式。如程式碼片段所示,我們調用定義在WebApplication類型中的靜態工廠方法CreateBuilder根據指定的命令行參數(args)創建一個WebApplicationBuilder對象,並調用其Build方法構建出對代表承載Web應用的WebApplication對象。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run(context => context.Response.WriteAsync("Hello World!"));
app.Run();
接下來我們調用了它的兩個Run方法,調用得第一個Run方法是IApplicationBuilder介面(WebApplication類型實現了該介面)的擴展方法是為了註冊中間件,調用第二個Run方法才是啟動WebApplication對象代表的應用。由於我們並沒有在WebApplicationBuilder對象上作任何設置,所以我們可以按照如下的方式調用WebApplication的靜態Create方法將WebApplication對象創建出來。
var app = WebApplication.Create(args);
app.Run(context => context.Response.WriteAsync("Hello World!"));
app.Run();
值得一提的是,之前的兩種承載方式都傾向於將初始化操作定義在註冊的Startup類型中,這種編程在Mininal API中不再被支援,所以如下的程式雖然可以成功編譯,但是執行的時候會拋出異常。
var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseStartup<Startup>(); var app = builder.Build(); app.Run();