1. 程式人生 > 其它 >ASP.NET Core 6框架揭祕例項演示[23]:ASP.NET Core應用承載方式的變遷

ASP.NET Core 6框架揭祕例項演示[23]:ASP.NET Core應用承載方式的變遷

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();