一題多解,ASP.NET Core應用啟動初始化的N種方案[下篇]

[接上篇]「天下大勢,分久必合,合久必分」,ASP.NET應用通過GenericWebHostService這個承載服務被整合到基於IHostBuilder/IHost的服務承載系統中之後,也許微軟還是意識到Web應用和後台服務的承載方式還是應該加以區分,於是推出了基於WebApplicationBuilder/WebApplication的承載方式。我們可以將其稱為第三代承載模式,它有一個官方的名稱叫做「Minimal API」。Minimal API同樣面臨向後兼容的問題,而且這次需要同時兼容前面兩代承載模式,所以我們會發現「上篇」中提到的一系列初始化操作有了更多實現方式。[本文部分內容來源於《ASP.NET Core 6框架揭秘》第15章]

目錄
一、Minimal API
二、推薦編程方式
三、承載環境
四、承載配置
五、應用配置
六、服務註冊
七、中間件註冊
八、Startup類型不再被支援

一、Minimal API

基於Minimal API的第三代應用承載方式的推出並非又回到了起點,因為底層的承載方式其實沒有改變,它只是在上面再封裝了一層而已。新的應用承載方式依然採用「構建者(Builder)」模式,核心的兩個對象分別為WebApplication和WebApplicationBuilder,代表承載應用的WebApplication對象由WebApplicationBuilder對象進行構建。第二代承載模式需要提供針對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。

二、推薦編程方式

我們再次使用[上篇]提供的實例來演示承載配置、應用配置、承載環境、服務註冊和中間件在Minimal API下的標準編程方式。該演示實例會註冊如下這個FoobarMiddleware中間件,後者利用注入的IHandler服務完成請求的處理工作。作為IHandler介面的默認實現類型,Handler利用構造函數注入的IOptions<FoobarbazOptions>對象得到配置選項FoobarbazOptions,並將其內容作為請求的響應。

public class FoobarMiddleware
{
    private readonly RequestDelegate _next;
    public FoobarMiddleware(RequestDelegate _) { }
    public Task InvokeAsync(HttpContext httpContext, IHandler handler) => handler.InvokeAsync(httpContext);
}

public interface IHandler
{
    Task InvokeAsync(HttpContext httpContext);
}

public class Handler : IHandler
{
    private readonly FoobarbazOptions _options;
    private readonly IWebHostEnvironment _environment;

    public Handler(IOptions<FoobarbazOptions> optionsAccessor, IWebHostEnvironment environment)
    {
        _options = optionsAccessor.Value;
        _environment = environment;
    }

    public Task InvokeAsync(HttpContext httpContext)
    {
        var payload = @$"
Environment.ApplicationName: {_environment.ApplicationName}
Environment.EnvironmentName: {_environment.EnvironmentName}
Environment.ContentRootPath: {_environment.ContentRootPath}
Environment.WebRootPath: {_environment.WebRootPath}
Foo: {_options.Foo}
Bar: {_options.Bar}
Baz: {_options.Baz}
";
        return httpContext.Response.WriteAsync(payload);
    }
}

public class FoobarbazOptions
{
    public string Foo { get; set; } = default!;
    public string Bar { get; set; } = default!;
    public string Baz { get; set; } = default!;
}

我們會利用與當前「承載環境」對應配置來綁定配置選項FoobarbazOptions,後者的三個屬性分別來源於三個獨立的配置文件。其中settings.json被所有環境共享,settings.dev.json針對名為「dev」的開發環境。我們為承載環境提供更高的要求,在環境基礎上進步劃分子環境,settings.dev.dev1.json針對的就是dev下的子環境dev1。針對子環境的設置需要利用上述的承載配置來提供。

image_thumb9

如下所示的就是上述三個配置文件的內容。如果當前環境和子環境分別為dev和dev1,那麼配置選項FoobarbazOptions的內容將來源於這三個配置文件。細心的朋友可能還注意到了:我們並沒有放在默認的根目錄下,而是放在創建的resources目錄下,這是因為我們需要利用針對承載環境的設置改變ASP.NET Core應用存放內容文件和Web資源文件的根目錄。

settings.json
{
  "Foo": "123"
}

settings.dev.json
{
  "Bar": "abc"
}

settings.dev.dev1.json
{
  "Baz": "xyz"
}

如下所示的程式碼體現了承載配置、應用配置、承載環境、服務註冊和中間件註冊這五種初始化操作在Minimal API中的標準編程方式。與承載環境相關的承載配置(環境名稱和內容文件與Web資源文件根目錄)被定義在WebApplicationOptions配置選項上,並將其作為參數調用WebApplication的靜態工廠方法CreateBuilder將WebApplicationBuilder對象構建出來。WebApplicationBuilder的Configuration屬性返回一個ConfigurationManager對象,由於它同時實現了IConfigurationBuilder和IConfiguration介面,所以我們利用利用它來設置配置源,並且能夠確保配置原提供的配置能夠實時反映到這個對象上。從編程的角度來說,Minimal API不再刻意地區分承載配置和應用配置,因為針對它們的設置都由這個ConfigurationManager對象來完成。我們利用這個對象將表示「子環境名稱」的承載配置進行了設置。

using App;
var options = new WebApplicationOptions
{
    Args = args,
    EnvironmentName = "dev",
    ContentRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources"),
    WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")
};
var appBuilder = WebApplication.CreateBuilder(options);
appBuilder.Configuration["SubEnvironment"] = "dev1";
appBuilder.Configuration
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.{appBuilder.Configuration["SubEnvironment"]}.json", optional: true);
appBuilder.Services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(appBuilder.Configuration);
var app = appBuilder.Build();
app.UseMiddleware<FoobarMiddleware>();
app.Run();

在完成了針對承載配置(含承載環境)的設置後,我們利用同一個ConfigurationManager對象完成針對應用配置的設置。具體來說,我們針對當前環境註冊了三個對應的配置文件,定位配置文件的環境名稱來源於WebApplicationBuilder的Environment屬性返回的IWebHostEnvironment對象,而子環境則直接從ConfigurationManager對象中提取。

WebApplicationBuilder的Services屬性返回用來存放服務註冊的IServiceCollection對象,所以需要的服務註冊直接添加到這個集合中就可以了。由於WebApplicationBuilder自身能夠提供承載環境和配置,所以針對環境以及當前配置進行針對性的服務註冊變得更加直接。我們利用這個IServiceCollection對象完成了針對IHandler/Handler的註冊,以及將配置綁定到FoobarbazOptions配置選項上。

在此之後,我們調用WebApplicationBuilder的Build方法將代表Web應用的WebApplication對象構建出來。由於後者的類型實現了IApplicationBuilder介面,所以我們可以直接利用它來完成中間件的註冊,我們自定義的FoobarMiddleware中間件就可以調用它的UseMiddleware<TMiddleware>方法進行註冊的。程式啟動之後,利用瀏覽器的請求會得到如下圖所示的結果。

image

三、承載環境

承載環境(環境名稱、內容文件根目錄和Web資源文件根目錄)相關的承載配置在Minimal API只支援如下三種設置方式:

  • 利用WebApplicationOptions(如我們提供的演示程式)
  • 利用命令行參數
  • 利用環境變數

我們按照如下的方式對演示程式進行了改寫,摒棄了WebApplicationOptions配置選項,改用三個對應的環境變數。由於環境變數會默認作為配置源,所以自然也可以利用環境變數設置子環境名稱。

Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "dev");
Environment.SetEnvironmentVariable("ASPNETCORE_SUBENVIRONMENT", "dev1");
Environment.SetEnvironmentVariable("ASPNETCORE_CONTENTROOT", Path.Combine(Directory.GetCurrentDirectory(), "resources"));
Environment.SetEnvironmentVariable("ASPNETCORE_WEBROOT", Path.Combine(Directory.GetCurrentDirectory(), "resources", "web"));

var appBuilder = WebApplication.CreateBuilder(args);

appBuilder.Configuration
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.{appBuilder.Configuration["SubEnvironment"]}.json", optional: true);
appBuilder.Services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(appBuilder.Configuration);
var app = appBuilder.Build();
app.UseMiddleware<FoobarMiddleware>();
app.Run();

由於WebApplicationBuilder利用WebHost屬性提供的ConfigureWebHostBuilder(實現了IWebHostBuilder介面)對象來兼容原來定義在IWebHostBuilder介面上的API,有的人可以會覺得我們一定也能夠像之前那樣利用這個對象來設置承載環境,我們不妨來試試是否可行。如下面的程式碼片段所示,我們直接調用該對象的UseEnvironment、UseContentRoot和UseWebRoot方法對環境名稱和內容文件與Web資源文件根目錄進行了設置。

var appBuilder = WebApplication.CreateBuilder(args);
appBuilder.WebHost
    .UseEnvironment("dev")
    .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources"))
    .UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources", "web"));

appBuilder.Configuration["SubEnvironment"] = "dev1";
appBuilder.Configuration
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.{appBuilder.Configuration["SubEnvironment"]}.json", optional: true);
appBuilder.Services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(appBuilder.Configuration);
var app = appBuilder.Build();
app.UseMiddleware<FoobarMiddleware>();
app.Run();

不幸的是,當我們啟動程式之後會拋出如下所示的異常,並提示環境名稱不能更改(其他承載環境屬性也是一樣),推薦使用WebApplicationOptions配置選項。由於承載環境是承載配置的範疇,但是Minimal API並沒有刻意將兩者區分開來,因為所有配置都實時體現在WebApplicationBuilder的Configuration屬性返回的ConfigurationManager對象上。承載環境需要在最開始就被確定下來,因為後續後續配置的設置和服務註冊都依賴於它,所以WebApplicationBuilder對象一旦被創建,承載環境就會固定下來,不能在改變。

image

可能有人還不死心,想到WebApplicationBuilder的Host屬性不是還提供了一個ConfigureHostBuilder(實現了IHostBuilder介面)對象嗎?我們是否可以按照如下的方式利用這個對象來設置承載環境相呢。很遺憾,我們同樣會得到上面這個錯誤。

var appBuilder = WebApplication.CreateBuilder(args);
appBuilder.Host
    .UseEnvironment("dev")
    .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources"));
appBuilder.WebHost
    .UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "resources", "web"));

appBuilder.Configuration["SubEnvironment"] = "dev1";
appBuilder.Configuration
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.{appBuilder.Configuration["SubEnvironment"]}.json", optional: true);
appBuilder.Services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(appBuilder.Configuration);
var app = appBuilder.Build();
app.UseMiddleware<FoobarMiddleware>();
app.Run();

不論是IWebHostBuilder的UseEnvironment、UseContentRoot和UseWebRoot方法,還是IHostBuilder的UseEnvironment和UseContentRoot方法,它們最終都是對配置系統的更新,那麼我們是否可以利用WebApplicationBuiler提供的ConfigurationManager對象按照如下的方式直接修改與承載環境相關的配置呢?

var appBuilder = WebApplication.CreateBuilder(args);

appBuilder.Configuration["Environment"] = "dev";
appBuilder.Configuration["SubEnvironment"] = "dev1";
appBuilder.Configuration["ContentRoot"] = Path.Combine(Directory.GetCurrentDirectory(), "resources");
appBuilder.Configuration["WebRoot"] = Path.Combine(Directory.GetCurrentDirectory(), "resources","web");
var app = appBuilder.Build();
app.MapGet("/", (IWebHostEnvironment environment) => Results.Json(environment, new JsonSerializerOptions {  WriteIndented = true}));
app.Run();

在配置了與承載環境相關的幾個屬性之後,我們註冊了一個針對根路徑的路由,路由註冊里會直接以JSON的形式返回當前承載環境。程式運行之後,針對根路徑的請求會得到如下所示的輸出結果,可以看出利用配置對承載環境的設置並沒有生效。

image

四、承載配置

承載配置會影響應用配置,比如針對演示實例的應用配置在設置的時候需要使用到對當前「子環境名稱」的設置。承載環境還會影響服務註冊,我們針對設置的「子環境」進行針對性的服務註冊也是一個常見的需求。由於Minimal API將這兩種類型的配置都集中到WebApplicationBuilder提供的ConfigurationManager對象上,所以針對承載配置的設置應該放在服務註冊和設置應用配置之前。

由於WebApplicationBuilder可以為我們提供IWebHostBuilder和IHostBuilder,所以只要不涉及承載環境相關的幾個預定義配置,其他承載配置(比如演示實例涉及的子環境名稱)完全可以利用這兩個對象進行設置。下面的程式碼片段演示了通過調用IWebHostBuilder的UseSettings方法來設置子環境名稱。

var options = new WebApplicationOptions
{
    Args = args,
    EnvironmentName = "dev",
    ContentRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources"),
    WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")
};
var appBuilder = WebApplication.CreateBuilder(options);
appBuilder.WebHost.UseSetting("SubEnvironment", "dev1");
appBuilder.Configuration
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.{appBuilder.Configuration["SubEnvironment"]}.json", optional: true);
appBuilder.Services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(appBuilder.Configuration);
var app = appBuilder.Build();
app.UseMiddleware<FoobarMiddleware>();
app.Run();

子環境名稱同樣可以按照如下的方式利用IHostBuilder的ConfigureHostConfiguration方法進行設置。

var options = new WebApplicationOptions
{
    Args = args,
    EnvironmentName = "dev",
    ContentRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources"),
    WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")
};
var appBuilder = WebApplication.CreateBuilder(options);
appBuilder.Host.ConfigureHostConfiguration(config => config.AddInMemoryCollection(
    new Dictionary<string, string> { { "SubEnvironment" ,"dev1" } }));
appBuilder.Configuration
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.{appBuilder.Configuration["SubEnvironment"]}.json", optional: true);
appBuilder.Services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(appBuilder.Configuration);
var app = appBuilder.Build();
app.UseMiddleware<FoobarMiddleware>();
app.Run();

五、應用配置

Minimal API下針對應用配置的設置,最簡單的方式莫過於上面演示的直接使用WebApplicationBuilder提供的ConfigurationManager對象。但是IWebHostBuilder和IHostBuilder介面的ConfigureAppConfiguration方法依然是可以使用的,所以演示實例針對應用配置的設置可以改寫成如下兩種形式。

var options = new WebApplicationOptions
{
    Args = args,
    EnvironmentName = "dev",
    ContentRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources"),
    WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")
};
var appBuilder = WebApplication.CreateBuilder(options);
appBuilder.Host.ConfigureHostConfiguration(config => config.AddInMemoryCollection(
    new Dictionary<string, string> { { "SubEnvironment" ,"dev1" } }));
appBuilder.WebHost.ConfigureAppConfiguration((context, config) => config
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true));
appBuilder.Services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(appBuilder.Configuration);
var app = appBuilder.Build();
app.UseMiddleware<FoobarMiddleware>();
app.Run();
var options = new WebApplicationOptions
{
    Args = args,
    EnvironmentName = "dev",
    ContentRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources"),
    WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")
};
var appBuilder = WebApplication.CreateBuilder(options);
appBuilder.Host.ConfigureHostConfiguration(config => config.AddInMemoryCollection(
    new Dictionary<string, string> { { "SubEnvironment" ,"dev1" } }));
appBuilder.Host.ConfigureAppConfiguration((context, config) => config
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true));
appBuilder.Services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(appBuilder.Configuration);
var app = appBuilder.Build();
app.UseMiddleware<FoobarMiddleware>();
app.Run();

六、服務註冊

既然WebApplicationBuilder的Services屬性已經提供了用來存放服務註冊的IServiceCollection對象,那麼Minimal API下可以直接可以利用它來註冊我們所需的服務。但是IWebHostBuilder和IHostBuilder介面的ConfigureServices方法依然是可以使用的,所以演示實例針對服務的註冊可以改寫成如下兩種形式。

var options = new WebApplicationOptions
{
    Args = args,
    EnvironmentName = "dev",
    ContentRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources"),
    WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")
};
var appBuilder = WebApplication.CreateBuilder(options);
appBuilder.Configuration["SubEnvironment"] = "dev1";
appBuilder.Configuration
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.{appBuilder.Configuration["SubEnvironment"]}.json", optional: true);
appBuilder.WebHost.ConfigureServices((context, services) =>services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(context.Configuration));
var app = appBuilder.Build();
app.UseMiddleware<FoobarMiddleware>();
app.Run();
var options = new WebApplicationOptions
{
    Args = args,
    EnvironmentName = "dev",
    ContentRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources"),
    WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")
};
var appBuilder = WebApplication.CreateBuilder(options);
appBuilder.Configuration["SubEnvironment"] = "dev1";
appBuilder.Configuration
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{appBuilder.Environment.EnvironmentName}.{appBuilder.Configuration["SubEnvironment"]}.json", optional: true);
appBuilder.Host.ConfigureServices((context, services) =>services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(context.Configuration));
var app = appBuilder.Build();
app.UseMiddleware<FoobarMiddleware>();
app.Run();

七、中間件註冊

中間件總是註冊到IApplicationBuilder對象上,由於WebApplicationBuilder創建的WebApplication對象同時也是一個IApplicationBuilder對象,所以最簡便快捷的中間件註冊方法莫過於直接使用WebApplication對象。可能有人覺得也可以利用IWebHostBuiller的Configure方法來註冊中間件,比如將我們的演示實例改寫成如下的形式。

var options = new WebApplicationOptions
{
    Args = args,
    EnvironmentName = "dev",
    ContentRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources"),
    WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")
};
var appBuilder = WebApplication.CreateBuilder(options);
appBuilder.Host.ConfigureHostConfiguration(config => config.AddInMemoryCollection(
    new Dictionary<string, string> { { "SubEnvironment" ,"dev1" } }));
appBuilder.Host.ConfigureAppConfiguration((context, config) => config
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true));
appBuilder.Services
    .AddSingleton<IHandler, Handler>()
    .Configure<FoobarbazOptions>(appBuilder.Configuration);
appBuilder.WebHost.Configure(app => app.UseMiddleware<FoobarMiddleware>());
var app = appBuilder.Build();
app.Run();

實際上是不可以的,啟動改寫後的程式會拋出如下的NotSupportedException異常,並提示定義在WebApplicationBuilder的WenHost返回的ConfugureWebHostBuilder對象的Configure方法不再被支援,中間件的註冊只能利用WebApplication對象來完成。

image

八、Startup類型不再被支援

在Minimal API之前,將服務註冊、中間件註冊以及針對依賴注入容器的設置放在Startup類型中是一種被推薦的做法,但是這種編程方法在Minimal API中也不再被支援。

var options = new WebApplicationOptions
{
    Args = args,
    EnvironmentName = "dev",
    ContentRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources"),
    WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "resources", "web")
};
var appBuilder = WebApplication.CreateBuilder(options);
appBuilder.Host.ConfigureHostConfiguration(config => config.AddInMemoryCollection(
    new Dictionary<string, string> { { "SubEnvironment" ,"dev1" } }));
appBuilder.Host.ConfigureAppConfiguration((context, config) => config
    .AddJsonFile(path: "settings.json", optional: false)
    .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.json", optional: true)
    .AddJsonFile(path: $"settings.{context.HostingEnvironment.EnvironmentName}.{context.Configuration["SubEnvironment"]}.json", optional: true));
appBuilder.WebHost.UseStartup<Startup>();
var app = appBuilder.Build();
app.Run();

public class Startup
{
    public Startup(IConfiguration configuration)=> Configuration = configuration;
    public IConfiguration Configuration { get; }
    public void ConfigureServices(IServiceCollection services)
    {
        services
             .AddSingleton<IHandler, Handler>()
            .Configure<FoobarbazOptions>(Configuration);
    }
    public void Configure(IApplicationBuilder app)=>app.UseMiddleware<FoobarMiddleware>();
}

上面的程式將服務註冊和中間件註冊放在按照約定定義的Startup類型中,在利用WebApplicationBuilder的WebHost屬性得到提供的ConfigureWebHostBuilder對象之後,我們調用其UseStartup方法對這個Startup類型進行了註冊。遺憾的是,應用啟動時同樣會得到如下所示類似的NotSupportedException異常。

image

一題多解,ASP.NET Core應用啟動初始化的N種方案[上篇]
一題多解,ASP.NET Core應用啟動初始化的N種方案[下篇]