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

2022-07-11 09:14:02

[接上篇]「天下大勢,分久必合,合久必分」,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種方案[下篇]