免费的黄冈网站有哪些平台?,成全视频免费观看在线看收索,wordpress主题无法创建目录,天元建设集团有限公司第十一建筑工程分公司我的博客换新家啦#xff0c;新的地址为#xff1a;https://clrdaily.com :-D今天我们来一起思考一下如何在不同的环境应用不同的配置。这里的配置不仅仅指 IConfiguration 还包含 IWebHostBuilder 的创建过程和 Startup 的初始化过程。0 太长不读环境造成的差异在架构中基本… 我的博客换新家啦新的地址为https://clrdaily.com :-D今天我们来一起思考一下如何在不同的环境应用不同的配置。这里的配置不仅仅指 IConfiguration 还包含 IWebHostBuilder 的创建过程和 Startup 的初始化过程。0 太长不读环境造成的差异在架构中基本体现在 Infrastructure 中的各个 Adapter 中。而不应当入侵应用程序内部在 ASP.NET Core 中我们需要考虑如何将这些 Adapter一放在 service collection 中 二可选添加到 pipeline 中。ASP.NET Core 默认提供了一系列手段来判断当前的环境只不过这些手段的设计奇怪且不完整。IWebHostBuilder 的配制方法大多和环境相关但 UseSetting 和环境无关。我们应当应用开闭原则将相同环境的配置聚合起来不同环境的配置进行统一抽象。方便维护和扩展。当我们进行设计的时候需要注意不要将思路局限在 Framework 的设计上而应当切实考虑我们真正希望解决的问题。1 架构层面的思考Web Service 的开发和部署过程会涉及若干环境。总的来说可以分为开发环境和部署环境。而部署环境往往又分为 QA、Stage 和 Production 等。对于不同的环境应用程序可能需要应用不同的配置或实现。还是回到架构的层面上如下图那么这种不同应该体现在架构的哪一个层面上呢应当让这些不同体现在 Infrastructure 的那些 Adapters 上。因为 Adapter 是其中直接和环境相关的部分。用一个典型的例子来表示。假定一个注册用户 Account 的业务。在 Application Service 层面我们提供了如下的接口public class AccountRegistrationService { public AccountRegistrationResult Register(AccountRegistrationRequest request) { Account account this.repository.CreateDetached(); // initialize account from request account.Save(); return AccountRegistrationResult.Create(account); }}在 Domain 层面我们有代表领域对象 Account 的类型 Account。Account 类型的 Save() 方法可以保存账户信息其中的实现类似public class Account { ... public void Save() { this.repository.Save(this); }}而其中的 repository 则依赖 UnitOfWork 而 UnitOfWork 则可能依赖于具体的持久化实现或者依赖于其他远程服务public class AccountRepository { readonly IUnitOfWork session; public Account CreateDetached() { return new Account(this); } public void Save(Account account) { this.session.RegisterNew(account); }}在这个例子中AccountService 属于 Application Service 层面Account 和 AccountRepository 则属于 Domain 层面。这两层的依赖关系是 Application Service 依赖于 Domain。而 Domain 中的 UnitOfWork 则是一个接口。假设我们需要将数据写入数据库。则这个接口的实现需要持久化的支持例如它需要使用特定的 IDbConnection Adapter。即 IUnitOfWork 的实现位于 Infrastructure 层并在 Infrastructure 层调用 Adapter 向 DB 中写入信息。而对于不同的环境则可以使用不同的实现例如对于运行单元测试的环境我们不妨叫她 Test 环境。这个 DB 很有可能是一个 in memory 的 SQLite 数据库。而在生产环境则是 MySQL 的集群。应用程序的内部逻辑最终全部依赖与特定的抽象或接口。它们全部严密的包裹在 Infrastructure 之中并和外部环境完全隔离。而 Infrastructure 中的 Adapter 则负责联系外部环境。综上所述环境相关的变化应当全部封闭在 Infrastructure 中。2 ASP.NET Core 中的对应关系ASP.NET Core 应用程序中的组件的初始化由两个部分构成第一个部分就是将组件中的类型添加到依赖注入的 IServiceCollection 实例中以便进行创建第二个部分可选即将组件通过 IApplicationBuilder 添加到应用程序的处理流水线中。我们一个一个来思考。2.1 依赖注入ASP.NET Core Web Application 中用依赖注入来决定某种抽象的实现类型。但需要指出的是 ASP.NET 应用程序的依赖注入是分两个阶段进行的。我们将在另外一篇中介绍简单来说 ServiceCollection 的构建分为两个部分为了构建宿主环境而添加的类型Infrastructure 层为了应用程序本身而添加的 Framework例如 MvC和各种业务类型。Infrastructure 层Application Domain 层。而和环境相关的部分主要位于 “为了构建宿主环境而添加的类型” 中。这一部分的代码属于在 IStartup 初始化之前的 WebHostBuilder 构建代码中。一般来说我们习惯于将 UseStartup 调用放在 IWebHostBuilder 实例创建的最后那么也就是 UseStartup 之前的代码public static IWebHostBuilder CreateWebHostBuilder(string[] args){ return new WebHostBuilder() .UseKestrel() .ConfigureLogging(...) // // The configurations before UseStartup are environment specific // .UseStartupStartup();}2.2 流水线在流水线配置中主要考虑的是 Web 输入输出上的的变化。例如 Production 环境需要配置 SSL消除敏感 Header消除详细的 Error Information 等等。将组件配置到应用程序的流水线的操作是在 IStartup 接口的实现中进行的。定义 IStartup 接口实现的方式大体有两种第一种是调用 WebHostBuilderExtensions.Configure 方法另一种是使用 WebHostBuilderExtensions.UseStartup 方法。不论使用何种方式最终都会归结到对 IApplicationBuilder 的操作public void Configure(IApplicationBuilder app) { // building pipeline}在这个时候宿主初始化相关的类型已经全部可以使用了。因此取用环境相关的信息环境类型配置等就更方便了。3 落地ASP.NET Core 对这个环节的设计很奇怪。一方面它提供了非常底层的基于 IHostingEnvironment.EnvironmentName 的值来进行环境区分的方法。例如官方范例中往往会使用如下的代码new WebHostBuilder() .UseKestrel() .ConfigureLogging((context, logBuilder) { if (context.HostingEnvironment.IsDevelopment()) { ... } else if (context.HostingEnvironment.IsProduction()) { ... } else { ... } }) ...而另一方面却又在 Startup 上设计了命名的 Convension。例如class DevelopmentStartup {} // for Developmentclass ProductionStartup {} // for Productionclass Startup {} // fallback...webHostBuilder.UseStartup(assemblyName);又例如class Startup { public void ConfigureServices(IServiceCollection services) { } public void ConfigureStagingServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { } public void ConfigureStaging(IApplicationBuilder app, IHostingEnvironment env) { }}这些设计差异很大且每一个都不彻底。而在实际项目中环境属于一个扩展点而每一套环境的各项配置应当是内聚的。因此上述几种方式或多或少会增加维护上的成本。而较好的设计应当针对如下三个问题能够立刻说出我的系统支持几种环境每一种环境的各种类型的配置例如配置源、日志记录、HTTP Client、数据库是什么样子的有什么差异能不能用两步添加一个新的环境第一一次性创建一个新环境的所有配置第二将这个环境纳入到系统初始化过程中。为了达到这个要求需要考虑统一的实现手段。3.1 在 WebHost 开始构建之前我们并不能确定环境信息一个最简单的想法就是根据不同的环境采取两种完全不同的 WebHostBuilder 配置流程。例如WebHostBuilder builder new WebHostBuilderFactory().Create(env.EnvironmentName);遗憾的是这种设计本身是有问题的。首先若干环节都可以影响环境的最终确定包括当前 Session 的 ASPNETCORE_ENVIRONMENT 的值请参见 https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostBuilder.cs#L44Properties/launchSettings.json 中选定 Profile 中 ASPNETCORE_ENVIRONMENT 的值如果用 dotnet run 命令执行的话WebHostBuilder.UseEnvironment(name) 的参数值WebHostBuilder.UseSetting(key, value) 当 key 为 WebHostDefaults.EnvironmentKey 时的值。若 Host 在 IIS 中则 web.config 中关于 environmentVariable 的设置。因此只有在 WebHostBuilder 开始 Build 时我们才可以最终确定环境名称。3.2 UseSetting 并不是环境相关的另一种方案是包装 IWebHostBuilder 使其能够依据环境做出相应的 Dispatch。例如abstract class EnvironmentAwareWebHostBuilder : IWebHostBuilder { IWebHostBuilder UnderlyingBuilder { get; } protected abstract bool IsSupported(IHostingEnvironment hostingEnvironment); protected EnvironmentAwareWebHostBuilder(IWebHostBuilder underlyingBuilder) { // Validation omitted UnderlyingBuilder underlyingBuilder; } // ...}从而我们可以分别为不同的环境进行相应的配置。以 ConfigureService 方法为例public IWebHostBuilder ConfigureServices(ActionIServiceCollection configureServices){ UnderlyingBuilder.ConfigureServices( (context, services) { if (!IsSupported(context.HostingEnvironment)) { return; } configureServices(services); }); return this;}按照上述方式包装 ConfigureAppConfiguration这样就可以构造以下的扩展方法public static IWebHostBuilder UseEnvironment( this IWebHostBuilder builder, string environmentName, ActionIWebHostBuilder configureBuilder){ bool IsEnvironmentSupported(IHostingEnvironment h) h.IsEnvironment(config.environmentName); EnvironmentAwareWebHostBuilder environmentAwareBuilder new DelegatedWebHostBuilder(builder, IsEnvironmentSupported); config.configureBuilder(environmentAwareBuilder); return builder;}这种方案下的 WebHostBuilder 初始化逻辑就变成了webHostBuilder .UseEnvironment(Development, wb { wb .ConfigureService((ctx, cb) { ... }) .ConfigureLogging((lb) { ... }) ... }) .UseEnvironment(Production, wb { // configure for production });这样我们至少就可以用若干扩展方法类将不同环境完全分开了。但是这个实现方案是有问题的UseSetting 方法。IWebHostBuilder 所公开的方法中除了 Build、ConfigureServices 和 ConfigureAppConfiguration 之外还有第四个方法UseSetting。和上述 ConfigureXxx 方法不同UseSetting 方法执行完毕之后其影响马上生效而且该方法无法根据不同的环境作出变化。即如果我们使用了webHostBuilder .UseEnvironment(Development, wb wb.UseSetting(Foo, Bar)) .UseEnvironment(Production, wb wb.UseSetting(Foo, O_o));且当前环境为 Development 则 IConfiguration 实例的 Foo 对应的值为 O_o。这就会造成混淆。3.3 还是从扩展点来思考从第 2 节的论述中我们已经知道和环境相关的配置可能存在于宿主环境初始化过程中也可能存在 Startup 初始化过程中即 WebHost.Run 方法执行过程中。因此我们必须综合考虑这两个部分但是这个两个部分天生是不同的。那么强行进行统一也是不合适的。根据开闭原则我们还是应该从扩展点上来考虑。首先我们能够确定我们的 Adapter 有哪些。又有哪一些 Adapter 是和环境相关的。例如我们和环境相关的 Adapter 有 DB配置文件加载日志记录HttpClient在非 Development 环境中我们可能需要进行客户端证书验证在流水线创建过程中需要根据环境配置是否需要 HTTPS 强制跳转需要配置错误信息的详细程度等等。在梳理好这些内容后我们就能有针对性的创建方法对各个部分进行配置了我们可以使用工厂模式class WebHostConfigureFactory { ... public IWebHostConfigurator Create(string environmentName) { return cachedConfigurators[environmentName]; }}而每一个 IWebHostConfigurator 中都包含了所有的环境相关配置interface IWebHostConfigurator { void AddDatabase(IHostingEnvironment environment, IServiceCollection services); void LoadConfiguration(IHostingEnvironment environment, IConfigurationBuilder configBuilder); void ConfigureLogging(IHostingEnvironment environment, ILoggingBuilder loggingBuilder); void AddHttpClient(IHostingEnvironment environment, IServiceCollection services); void ConfigureHttpsRedirection(IHostingEnvironment environment, IConfiguration configuration, IApplicationBuilder builder); void ConfigureErrorHandler(IHostingEnvironment environment, IConfiguration configuration, IApplicationBuilder builder);}而这样我们为各个环境的扩展点建立了抽象从而统一配置过程static IWebHostBuilder CreateWebHostBuilder() { return new WebHostBuilder() .UseKestrel() // // Common configurations // .ConfigureServices((context, services) { IWebHostConfigurator configurator factory.Create(context.HostingEnvironment.EnvironmentName); configurator.AddDatabase(context.HostingEnvironment, services); configurator.AddHttpClient(context.HostingEnvironment, services); }) .ConfigureLogging((context, logBuilder) { factory .Create(context.HostingEnvironment.EnvironmentName) .ConfigureLogging(context.HostingEnvironment, logBuilder); }) .UseStartupStartup();}...class Startup { ... public void Configure(IApplicationBuilder app) { IWebHostConfigurator configurator factory.Create(hostingEnvironment.EnvironmentName); configurator.ConfigureHttpsRedirection(hostingEnvironment, configuration, app); configurator.ConfigureErrorHandler(hostingEnvironment, configuration, app); // Common configurations }}4 总结请跳到文章开头 :-D参考资料R. C. Martin and R. C. Martin, Clean architecture: a craftsman’s guide to software structure and design. London, England: Prentice Hall, 2018.Unit-of-work: https://martinfowler.com/eaaCatalog/unitOfWork.htmlDependency Injection in ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?viewaspnetcore-2.2App startup in ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?viewaspnetcore-2.2Use multiple environments in ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?viewaspnetcore-2.2如果您觉得本文对您有帮助也欢迎分享给其他的人。我们一起进步。欢迎关注我的微信公众号