ASP.NET应用通过GenericWebHostService这个承载服务被整合到基于IHostBuilder/IHost的服务承载系统中之后,也许微软还是意识到Web应用和后台服务的承载方式还是应该加以区分,于是推出了基于WebApplicationBuilder/WebApplication的承载方式。我们可以将其称为第三代承载模式,它有一个官方的名称叫做“Minimal API”。Minimal API同样面临向后兼容的问题,而且这次需要同时兼容前面两代承载模式。
public sealed class WebApplicationBuilder { // 堆代码 duidaima.com 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, ISupportsConfigureWebHostIWebHostBuilder和IHostBuilder接口都提供了设置配置和日志的方法,这两方面的设置都可以利用WebApplicationBuilder利用Configuration和Logging暴露出来的ConfigurationManager和ILoggingBuilder对象来实现。既然我们采用了Minimal API,那么我们就应该尽可能得使用WebApplicationBuilder类型提供的API。
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。针对子环境的设置需要利用上述的承载配置来提供。
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对象中提取。
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对象一旦被创建,承载环境就会固定下来,不能在改变。
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的形式返回当前承载环境。程序运行之后,针对根路径的请求会得到如下所示的输出结果,可以看出利用配置对承载环境的设置并没有生效。
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();
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();六、服务注册
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();七、中间件注册
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对象来完成。
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异常。