一题多解,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。针对子环境的设置需要利用上述的承载配置来提供。

434b6a9289b2dbfe186cdddbb675e475.png

如下所示的就是上述三个配置文件的内容。如果当前环境和子环境分别为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>方法进行注册的。程序启动之后,利用浏览器的请求会得到如下图所示的结果。

988e4a75ddfd6baa1e3e49eb647624ee.png

三、承载环境

承载环境(环境名称、内容文件根目录和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对象一旦被创建,承载环境就会固定下来,不能在改变。

f1308c4e093a1d41843f026dbb783cbc.png

可能有人还不死心,想到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的形式返回当前承载环境。程序运行之后,针对根路径的请求会得到如下所示的输出结果,可以看出利用配置对承载环境的设置并没有生效。

9079ed14f3e6867f4e2b33939d8b036a.png

四、承载配置

承载配置会影响应用配置,比如针对演示实例的应用配置在设置的时候需要使用到对当前“子环境名称”的设置。承载环境还会影响服务注册,我们针对设置的“子环境”进行针对性的服务注册也是一个常见的需求。由于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对象来完成。

70f56c6c0eaedf480e51e6a280c71618.png

八、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异常。

a1acfdce671315431c038f9029a4adbc.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/281764.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java jpa 模糊查询_JPA 以SQL实现分页不模糊查询(参数可能为空)

repository代码:package com.fancy.miniflow.repository;import java.util.List;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.JpaSpecificationExecutor;import org.springframework.data.jpa.repository.Q…

GitHub服务中断24小时11分钟事故分析报告\n

上周&#xff0c;GitHub经历了一次事故&#xff0c;导致服务降级24小时11分钟。虽然平台的某些部分不受事故影响&#xff0c;但仍然有多个内部系统受到了影响&#xff0c;向用户显示了过时且不一致的内容。所幸没有用户数据丢失&#xff0c;但针对几秒钟数据库写入的手动调整工…

8 旋转数组的最小数字

输入一个递增排序数组的一个旋转&#xff0c;输出旋转数组的最小元素例如1,2,3,4,5的一个旋转可以为3,4,5,1,2把一个数组的最开始若干个元素搬到数组的末尾&#xff0c;称之为数组的旋转 输出旋转数组的最小元素 C: 1 class Solution {2 public:3 int minInOrder(vector<…

软考新思维--2017年上半年信息系统项目管理师上午试题分析与答案(试题6-10题)...

2017年上半年信息系统项目管理师上午试题分析与答案&#xff08;试题1-5题&#xff09; 6.&#xff08;&#xff09;不是获取需求的方法。A、问卷调查B、会议讨论C、获取原型D、决策分析【软考新思维】需求是获取的得来的&#xff0c;不是决策得来的。 先是获取需求&#xff0c…

php 合并 字符串_PHP如何去重合并字符串

本篇文章主要给大家介绍PHP如何去重合并字符串。推荐教程&#xff1a;《PHP教程》对于PHP学习者来说&#xff0c;合并多个字符串&#xff0c;应该并不是很难。但是如果这多个字符串中&#xff0c;有相同元素&#xff0c;当我们想要合并他们并且要使其值具有唯一值。也就是说合并…

10.31T4 HAOI2010最长公共子序列 计数+容斥原理

2775 -- 【HAOI2010】最长公共子序列 Description 字符序列的子序列是指从给定字符序列中随意地&#xff08;不一定连续&#xff09;去掉若干个字符&#xff08;可能一个也不去掉&#xff09;后所形成的字符序列。令给定的字符序列X“x0&#xff0c;x1&#xff0c;…&#xff0…

软概(lesson 2):课堂测试

一、测试题目 二、完成过程 1.设计思想 ①连接mysql数据库 ②设计user类&#xff0c;增加参数 ③设计add类&#xff0c;向数据库内增加内容 ④设计addInput页面&#xff0c;完成录入操作 ⑤设计add页面&#xff0c;接收录入的参数&#xff0c;并调用add类函数 2.源代码 user.ja…

谷歌Gboard输入法新增“无痕模式”:仅在Chrome隐身窗口中适用

据外媒Android Police报道&#xff0c;如大家所知道的&#xff0c;Chrome浏览器中的“隐身模式”是为了防止你的私密浏览记录被其他人看到&#xff0c;但是&#xff0c;在这种模式下&#xff0c;你的输入法键盘依然会记住你输入的短语&#xff0c;为了阻止你的键盘在Chrome隐身…

php两个数组融合,php合并两个数组的方式有哪些

1、arrary_merge示例代码&#xff1a;$arr1 array(1, 2, 3, 4, 5);$arr2 array(1, 2, 6, 7, 8, 9, 10);$result1 array_merge($arr1, $arr2);$arr3 array("name" > "itbsl", "age" > 13, "sex" > "Male");$arr…

最近对latin-1这个字符集产生了不少好感

【简介】 最近我要解析一个数据库中间件的日志、这个中间件会在日志中记录SQL发往的后台DB ,执行耗时&#xff0c;对应的SQL&#xff1b;中间件直接把SQL写到 了日志中去&#xff0c;并没有对SQL进行适当的编码转换&#xff1b;理想情况下这个也不会有什么问题&#xff0c;不幸…

面象对象设计原则之六:迪米特原则(LeastKnowledge Principle, LKP)

迪米特法则来自于1987年美国东北大学(Northeastern University)一个名为“Demeter”的研究项目。迪米特法则又称为最少知识原则(LeastKnowledge Principle, LKP)&#xff0c;其定义如下&#xff1a; 迪米特法则(Law of Demeter, LoD)&#xff1a;一个软件实体应当尽可能少地与…

php symfony urlmatcher-gt;match,symfony路由组件(The Routing Component)

The Routing component 把HTTP request转换为一系列的配置参数.安装你有两种方式来安装这个组件:通过 Composer (symfony/routing on Packagist);使用官方的 Git repository (https://github.com/symfony/Routing)。然后, 需要Composer把vendor/autoload.php 这个文件提供 给 a…

R升级和包更新

1.R升级 # 安装包"installr" install.packages("installr") # 导入包 library(installr) # 升级 updateR() 2.包升级 # 包升级 update.packages() 3.安装包 # 选择镜像 options(reposstructure(c(CRAN"https://cran.cnr.berkeley.edu/"))) # 安装…

其他对象的表单

1.textarea&#xff1a; textarea对象就想是input对象中的text样式的表单&#xff0c;只不过是扩展过的text样式表单。它可以通过行&#xff08;rows&#xff09;属性和列&#xff08;cols&#xff09;属性来编辑文本域的大小。最常见于留言板、论坛时回帖时的文本框等。 <h…

WinForm(十三)WebView2

WebView是WinForm框架中一个控件&#xff0c;用来对网页信息交互&#xff0c;有时Web自己开发的&#xff0c;有时Web是三方的。下面通过一个例子来看看WebView2的使用。首先看Web的逻辑&#xff0c;是一个商品添加页面&#xff0c;用AlpineJS和BootStrap来开发的&#xff0c;业…

Fluent UDF【4】:C语言

Fluent UDF利用的是C语言&#xff0c;本文简单介绍在UDF中经常会用到的C语言常识。 本文部分内容来自UDF手册。 1 C语言中的注释 C语言中的注释利用/*及*/来实现。例如: /*这是一个注释*/ 注释也可以跨行实现&#xff0c;如: /*这是一个 跨行注释*/ 注意:在编写UDF的过程中&…

java 画砖块,钢笔画入门:教你画砖块

说到砖块很多朋友会想到搬砖&#xff0c;绘画吧今天要教大家用钢笔画一块砖&#xff0c;因为画建筑的时候经常要画砖墙&#xff0c;我们先从简单的砖块学起&#xff0c;之后绘画吧会给大家分享画一面砖墙的哦。绘制要点&#xff1a;本教程的主体物选择了一块有小残缺面的砖头。…

[转] Node.js的线程和进程

[From] http://www.admin10000.com/document/4196.html 前言 很多Node.js初学者都会有这样的疑惑&#xff0c;Node.js到底是单线程的还是多线程的&#xff1f;通过本章的学习&#xff0c;能够让读者较为清晰的理解Node.js对于单/多线程的关系和支持情况。同时本章还将列举一些让…

第三方支付异步通知的陷阱

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/j16421881/article/details/78703792 用户下单后调用第三方支付付款&#xff0c;然后接收第三方支付的异步通知&#xff0c;以便确认支付是否成功。 如下图 但异步通知可能…

js请求php文件 302,采集某个 url, js 请求 200,浏览器访问 302

/** 文件名: sso.js* 描述: 提供对 CAS 单点登录的封装** 功能说明&#xff1a;* 实现多个应用之间的单点登录( SSO )功能&#xff0c;应用可以部署在不同的域名。容器的退出直接写在头里&#xff0c;避免 JS 过多加载** 版本: 1.0.0.1* 作者: [email protected]* 日期&#xf…