在了解了作为服务宿主的 IHost 接口之后, 我们接着来认识一下作为宿主构建者的 IHostBuilder 接口. 如下面的代码片段所示, IHostBuilder 接口的核心方法 Build 用来提供由它构建的 IHost 对象. 除此之外, 它还具有一个字典类型的只读属性 Properties, 我们可以将它视为一个共享的数据容器.
- public interface IHostBuilder
- {
- IDictionary<object, object> Properties { get; }
- IHost Build();
- ...
- }
作为一个典型的设计模式, Builder 模式在最终提供给由它构建的对象之前, 一般会允许作相应的前期设置, IHostBuilder 针对 IHost 的构建也不例外. IHostBuilder 接口提供了一系列的方法, 我们可以利用它们为最终构建的 IHost 对象作相应的设置, 具体的设置主要涵盖两个方面: 针对配置系统的设置和针对依赖注入框架的设置.
一, 针对配置系统的设置
IHostBuilder 接口针对配置系统的设置体现在 ConfigureHostConfiguration 和 ConfigureAppConfiguration 方法上. 通过前面的实例演示, 我们知道 ConfigureHostConfiguration 方法涉及的配置主要是在服务承载过程中使用的, 是针对服务宿主的配置; ConfigureAppConfiguration 方法设置的则是供承载的 IHostedService 服务使用的, 是针对应用的配置. 不过前者最终会合并到后者之中, 我们最终得到的配置实际上是两者合并的结果.
- public interface IHostBuilder
- {
- IHostBuilder ConfigureHostConfiguration( Action<IConfigurationBuilder> configureDelegate);
- IHostBuilder ConfigureAppConfiguration( Action<HostBuilderContext, IConfigurationBuilder> configureDelegate);
- ...
- }
从上面的代码片段可以看出 ConfigureHostConfiguration 方法提供一个 Action<IConfigurationBuilder > 类型的委托作为参数, 我们可以利用它注册不同的配置源或者作相应的设置(比如设置配置文件所在目录的路径). 另一个方法 ConfigureAppConfiguration 的参数类型则是 Action<HostBuilderContext, IConfigurationBuilder>, 作为第一个参数的 HostBuilderContext 对象携带了与服务承载相关的上下文信息, 我们可以利用该上下文对配置系统作针对性设置.
HostBuilderContext 携带的上下文主要包含两个部分: 其一, 通过调用 ConfigureHostConfiguration 方法设置的针对宿主的配置; 其二, 当前的承载环境. 这两部分上下文信息分别对应着如下所示的 Configuration 和 HostingEnvironment 属性. 除此之外, HostBuilderContext 同样具有一个作为共享数据字典的 Properties 属性. 如果针对配置系统的设置与当前承载上下文无关, 我们可以调用如下这个同名的扩展方法, 该方法提供的参数依旧是一个 Action<IConfigurationBuilder > 类型的委托.
- public class HostBuilderContext
- {
- public IConfiguration Configuration { get; set; }
- public IHostEnvironment HostingEnvironment { get; set; }
- public IDictionary<object, object> Properties { get; }
- public HostBuilderContext(IDictionary<object, object> properties);
- }
- public static class HostingHostBuilderExtensions
- {
- public static IHostBuilder ConfigureAppConfiguration(this IHostBuilder hostBuilder, Action<IConfigurationBuilder> configureDelegate)
- => hostBuilder.ConfigureAppConfiguration((context, builder) =>configureDelegate(builder));
- }
二, 承载环境
任何一个应用总是针对某个具体的环境进行部署的, 我们将承载服务的部署环境称为承载环境. 承载环境通过 IHostEnvironment 接口表示, HostBuilderContext 的 HostingEnvironment 属性返回的就是一个 IHostEnvironment 对象. 如下面的代码片段所示, 除了表示环境名称的 EnvironmentName 属性之外, IHostEnvironment 接口还定义了一个表示当前应用名称的 ApplicationName 属性.
- public interface IHostEnvironment
- {
- string EnvironmentName { get; set; }
- string ApplicationName { get; set; }
- string ContentRootPath { get; set; }
- IFileProvider ContentRootFileProvider { get; set; }
- }
当我们编译某个. NET Core 项目的时候, 提供的代码文件 (.cs) 文件会转换成元数据和 IL 指令保存到生成的程序集中, 其他一些文件还可以作为程序集的内嵌资源. 除了这些面向程序集的文件之外, 一些文件还会以静态文件的形式供应用程序使用, 比如 web 应用三种典型的静态文件(JavaScript,CSS 和图片), 我们将这些静态文件称为内容文件 "Content File".IHostEnvironment 接口的 ContentRootPath 表示的就是存放这些内容文件的根目录所在的路径, 另一个 ContentRootFileProvider 属性对应的则是指向该路径的 IFileProvider 对象, 我们可以利用它获取目录的层次结构, 也可以直接利用它来读取文件的内容.
开发, 预发和产品是三种最为典型的承载环境, 如果采用 "Development","Staging" 和 "Production" 来对它们进行命名, 我们针对这三种承载环境的判断就可以利用如下三个扩展方法 (IsDevelopment,IsStaging 和 IsProduction) 来完成. 如果我们需要判断指定的 IHostEnvironment 对象是否属于某个具体的环境, 可以直接调用扩展方法 IsEnvironment. 从给出的代码片段我们不难看出针对环境名称的比较是不区分大小写的.
- public static class HostEnvironmentEnvExtensions
- {
- public static bool IsDevelopment(this IHostEnvironment hostEnvironment)
- => hostEnvironment.IsEnvironment(Environments.Development);
- public static bool IsStaging(this IHostEnvironment hostEnvironment)
- => hostEnvironment.IsEnvironment(Environments.Staging);
- public static bool IsProduction(this IHostEnvironment hostEnvironment)
- => hostEnvironment.IsEnvironment(Environments.Production);
- public static bool IsEnvironment(this IHostEnvironment hostEnvironment, string environmentName)
- => string.Equals(hostEnvironment.EnvironmentName, environmentName, StringComparison.OrdinalIgnoreCase);
- }
- public static class Environments
- {
- public static readonly string Development = "Development";
- public static readonly string Production = "Production";
- public static readonly string Staging = "Staging";
- }
IHostEnvironment 对象承载的 3 个属性都是通过配置的形式提供的, 对应的配置项名称为 "environment" 和 "contentRoot" 和 "applicationName", 它们对应着 HostDefaults 类型中三个静态只读字段. 我们可以调用如下这两个针对 IHostBuilder 接口的 UseEnvironment 和 UseContentRoot 扩展方法来设置环境名称和内容文件根目录路径. 从给出的代码片段可以看出, 该方法依旧是调用的 ConfigureHostConfiguration 方法. 如果没有对应用名称做显示设置, 入口程序集名称会作为当前应用名称.
- public static class HostDefaults
- {
- public static readonly string EnvironmentKey = "environment";
- public static readonly string ContentRootKey = "contentRoot";
- public static readonly string ApplicationKey = "applicationName";
- }
- public static class HostingHostBuilderExtensions
- {
- public static IHostBuilder UseEnvironment(this IHostBuilder hostBuilder, string environment)
- {
- return hostBuilder.ConfigureHostConfiguration(configBuilder =>
- {
- configBuilder.AddInMemoryCollection(new[]
- {
- new KeyValuePair<string, string>(HostDefaults.EnvironmentKey,environment)
- });
- });
- public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder, string contentRoot)
- {
- return hostBuilder.ConfigureHostConfiguration(configBuilder =>
- {
- configBuilder.AddInMemoryCollection(new[]
- {
- new KeyValuePair<string, string>(HostDefaults.ContentRootKey,
- contentRoot))
- });
- });
- }
- }
三, 针对依赖注入框架的设置
由于包括承载服务在内的所有依赖服务都是由依赖注入框架提供的, 所以 IHostBuilder 接口提供了更多的方法来对完成服务注册. 绝大部分用来注册服务的方法最终都调用了如下所示的 ConfigureServices 方法, 由于该方法提供的参数是一个 Action<HostBuilderContext, IServiceCollection > 类型的委托, 意味服务可以针对当前的承载上下文进行针对性注册. 如果注册的服务与当前承载上下文无关, 我们可以调用如下所示的这个同名的扩展方法, 该方法提供的参数是一个类型为 Action<IServiceCollection > 的委托对象.
- public interface IHostBuilder
- {
- IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate);
- ...
- }
- public static class HostingHostBuilderExtensions
- {
- public static IHostBuilder ConfigureServices(this IHostBuilder hostBuilder, Action<IServiceCollection> configureDelegate)
- => hostBuilder.ConfigureServices((context, collection) => configureDelegate(collection));
- }
在《承载长时间运行的服务[下篇]》针对日志的演示中, 我们调用了 IHostBuilder 接口的扩展方法 ConfigureLogging 注册了针对日志框架的核心服务, 如下的代码片段展示了这两个扩展方法重载的定义. 可以看出这两个方法的背后依旧是调用上面这个 ConfigureServices 方法, 具体的服务是通过调用 IServiceCollection 接口的 AddLogging 扩展方法注册的.
- public static class HostingHostBuilderExtensions
- {
- public static IHostBuilder ConfigureLogging(this IHostBuilder hostBuilder, Action<HostBuilderContext, ILoggingBuilder> configureLogging)
- => hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(context, builder)));
- public static IHostBuilder ConfigureLogging(this IHostBuilder hostBuilder, Action<ILoggingBuilder> configureLogging)
- => hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(builder)));
- }
IHostBuilder 接口提供了如下两个 UseServiceProviderFactory<TContainerBuilder > 方法重载, 我们可以利用它注册的 IServiceProviderFactory<TContainerBuilder > 对象实现对第三方依赖注入框架的整合. 除此之外, 该接口还提供了另一个 ConfigureContainer<TContainerBuilder > 为注册 IServiceProviderFactory<TContainerBuilder > 对象创建的容器作进一步设置.
- public interface IHostBuilder
- {
- IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory);
- IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory);
- IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate);
- }
我个人觉得. NET Core 依赖注入框架已经能够满足绝大部分应用开发的需求了, 所以真正与第三方依赖注入框架的整合其实并没有太多的必要. 我们知道原生的依赖注入框架使用 DefaultServiceProviderFactory 来提供作为依赖注入容器的 IServiceProvider, 针对它的注册由如下这两个 UseDefaultServiceProvider 扩展方法来完成.
- public static class HostingHostBuilderExtensions
- {
- public static IHostBuilder UseDefaultServiceProvider(this IHostBuilder hostBuilder, Action<ServiceProviderOptions> configure)
- => hostBuilder.UseDefaultServiceProvider((context, options) => configure(options));
- public static IHostBuilder UseDefaultServiceProvider(this IHostBuilder hostBuilder, Action<HostBuilderContext, ServiceProviderOptions> configure)
- {
- return hostBuilder.UseServiceProviderFactory(context =>
- {
- var options = new ServiceProviderOptions();
- configure(context, options);
- return new DefaultServiceProviderFactory(options);
- });
- }
- }
定义在 IHostBuilder 接口的 ConfigureContainer<TContainerBuilder > 方法提供的参数是一个类型为 Action<HostBuilderContext, TContainerBuilder > 的委托对象, 如果我们针对 TContainerBuilder 的设置与当前承载上下文无关, 我们也可以调用如下的这个简化的 ConfigureContainer<TContainerBuilder > 扩展方法, 它只需要提供一个 Action<TContainerBuilder > 对象作为参数就可以了.
- public static class HostingHostBuilderExtensions
- {
- public static IHostBuilder ConfigureContainer<TContainerBuilder>(this IHostBuilder hostBuilder, Action<TContainerBuilder> configureDelegate)
- {
- return hostBuilder.ConfigureContainer<TContainerBuilder>((context, builder) => configureDelegate(builder));
- }
- }
四, 创建并启动宿主
IHostBuilder 接口还具有如下这个 StartAsync 扩展方法, 它同时完成了针对 IHost 对象的创建和启动工作, 它的另一个 Start 方法是 StartAsync 方法的同步版本.
- public static class HostingAbstractionsHostBuilderExtensions
- {
- public static async Task<IHost> StartAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
- {
- var host = hostBuilder.Build();
- await host.StartAsync(cancellationToken);
- return host;
- }
- public static IHost Start(this IHostBuilder hostBuilder) => hostBuilder.StartAsync().GetAwaiter().GetResult();
- }
服务承载系统[1]: 承载长时间运行的服务[上篇]
服务承载系统[2]: 承载长时间运行的服务[下篇]
服务承载系统[3]: 总体设计[上篇]
服务承载系统[4]: 总体设计[下篇]
服务承载系统[5]: 承载服务启动流程[上篇]
服务承载系统[6]: 承载服务启动流程[下篇]
来源: https://www.cnblogs.com/artech/p/inside-asp-net-core-09-04.html