zoukankan      html  css  js  c++  java
  • ASP.NET Core 3 源码解析 — [7]通用主机

    尊重作者劳动成果,转载请注明出处,谢谢!

    目录

    1.主机与托管服务

      1.1 主机

      1.2 托管服务

    2.配置与创建主机

      2.1 加载主机及应用配置

      2.2 配置依赖注入框架

      2.3 创建主机

    3.实现解析

      3.1 设计模型

      3.2 Host

      3.3 HostBuilder

    4.初始化 HostBuilder 并启动主机

      4.1 CreateDefaultBuilder 方法

      4.2 Run 扩展方法

    1.主机与托管服务

    1.1 主机

    在 Hosting 系统中,主机是对一个应用程序的抽象,启动和关闭应用程序的本质就是启动和关闭作为托管服务宿主的主机对象。主机也是封装应用资源的对象,例如:依赖注入(DI)、Logging、Configuration 和 IHostedService 等。主机的模型如下图所示:

    在 .NET Core 中,IHost 接口表示主机,定义如下:

    public interface IHost : IDisposable
    {
        IServiceProvider Services { get; }
        Task StartAsync(CancellationToken cancellationToken = default);
        Task StopAsync(CancellationToken cancellationToken = default);
    }
    • Services:该属性是一个 IServiceProvider 对象,代表依赖注入容器,其中包含了主机默认注册的服务与配置和应用注册的服务和配置。
    • StartAsync:该方法用于启动主机。当主机被启动时,它会通过DI容器(Services 属性)找到所有注册的 IHostedService 服务,并依次调用其 StartAsync 方法。在 web 应用中,其中一个 IHostedService 实现(GenericWebHostService)是启动 HTTP 服务器的 web 服务,ASP.NET Core 正是通过该实现来实现对 HTTP 请求的监听、处理及响应。
    • StopAsync:该方法用于关闭主机,并以与启动相反的顺序调用 IHostedService 的 StopAsync 方法。

    1.2 托管服务

    托管服务被定义为 IHostedService 接口,定义如下:

    public interface IHostedService
    {
        Task StartAsync(CancellationToken cancellationToken);
        Task StopAsync(CancellationToken cancellationToken);
    }

    托管服务(IHostedService)被托管或承载于主机(IHost)之中,服务的启动和停止随着主机的启动和停止,主机可以托管多个服务。 针对托管服务的注册,一般通过 AddHostedService 扩展方法来实现,如下代码所示:

    public static class ServiceCollectionHostedServiceExtensions
    {
        public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services) where THostedService : class, IHostedService
        {
            services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>());
            return services;
        }
    
        public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services, Func<IServiceProvider, THostedService> implementationFactory) where THostedService : class, IHostedService
        {
            services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService>(implementationFactory));
            return services;
        }
    }

    注意:针对托管服务的注册一般采用Singleton模式。

    2.配置与创建主机

    .NET Core 允许我们在创建主机之前对其进行配置,(例如可以添加 xml、ini 文件等配置源,注册自定义的依赖服务,以及将内置的 DI 容器替换为第三方的 DI 容器等)。针对主机的创建和配置是通过 IHostBuilder 接口完成的,定义如下:

    public interface IHostBuilder
    {
        IDictionary<object, object> Properties { get; }
    
        IHost Build();
        IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate);
        IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate);
        IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate);
        IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate);
        IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory);
        IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory);
    }

    HostBuilderContext 表示在创建主机过程中提供的上下文信息,其中包括了主机环境对象,主机的配置对象,使用该上下文信息可以针对不同的运行环境进行服务(依赖注入服务)的注册等。HostBuilderContext 的定义如下:

    public class HostBuilderContext
    {
        public HostBuilderContext(IDictionary<object, object> properties)
        {
            Properties = properties ?? throw new System.ArgumentNullException(nameof(properties));
        }
    
        public IHostEnvironment HostingEnvironment { get; set; }
        public IConfiguration Configuration { get; set; }
        public IDictionary<object, object> Properties { get; }
    }

    IHostEnvironment 表示主机环境,定义如下:

    public interface IHostEnvironment
    {
        string EnvironmentName { get; set; }
        string ApplicationName { get; set; }
        string ContentRootPath { get; set; }
        IFileProvider ContentRootFileProvider { get; set; }
    }
    • EnvironmentName:环境名称,.NET Core 内置了3种环境名称,即 Development(开发)、Staging(预发)、Production(生产)。在未指定环境名称时,默认的环境名称为 Production。
    • ApplicationName:应用程序名称。
    • ContentRootPath:内容文件根目录路径。
    • ContentRootFileProvider:表示内容文件根目录的 IFileProvider 对象。

    在创建主机之前,我们可以针对主机进行相应的配置,主要包括以下两个方面的配置:针对配置系统的配置和针对依赖注入框架的配置。当调用 IHostBuilder 的 Build 方法创建主机时,这些配置会被一一被提取出来执行,并将最终得到的配置和服务注入到 DI 中。

    2.1 加载主机及应用配置

    • ConfigureHostConfiguration:该方法用于配置主机配置,主机配置用于创建 HostBuilderContext 上下文信息。
    • ConfigureAppConfiguration:该方法用于配置应用配置,即配置托管服务所需要的配置。

    HostBuilderContext 中的 Configuration 属性和 HostingEnvironment 属性首先是通过 ConfigureHostConfiguration 方法注册的配置进行初始化的,随后 Configuration 会和在 ConfigureAppConfiguration 方法中注册的配置进行合并。

    IHostBuilder 接口提供了相应的扩展方法来设置主机环境的名称和内容根目录,如下代码所示:

    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  ?? throw new ArgumentNullException(nameof(environment)))
                });
            });
        }
    
        public static IHostBuilder UseContentRoot(this IHostBuilder hostBuilder, string contentRoot)
        {
            return hostBuilder.ConfigureHostConfiguration(configBuilder =>
            {
                configBuilder.AddInMemoryCollection(new[]
                {
                    new KeyValuePair<string, string>(HostDefaults.ContentRootKey, contentRoot  ?? throw new ArgumentNullException(nameof(contentRoot)))
                });
            });
        }
    }
    
    public static class HostDefaults
    {
        /// <summary>
        /// The configuration key used to set <see cref="IHostEnvironment.ApplicationName"/>.
        /// </summary>
        public static readonly string ApplicationKey = "applicationName";
    
        /// <summary>
        /// The configuration key used to set <see cref="IHostEnvironment.EnvironmentName"/>.
        /// </summary>
        public static readonly string EnvironmentKey = "environment";
    
        /// <summary>
        /// The configuration key used to set <see cref="IHostEnvironment.ContentRootPath"/>
        /// and <see cref="IHostEnvironment.ContentRootFileProvider"/>.
        /// </summary>
        public static readonly string ContentRootKey = "contentRoot";
    }

    UseEnvironment 和 UseContentRoot 扩展方法均调用了 ConfigureHostConfiguration 方法,将内存数据(提供的配置值)添加到对应配置项中。对应配置项的 Key 值是通过 HostDefaults 定义的。

    2.2 配置依赖注入框架

    • ConfigureServices:该方法用于注册应用程序所需的依赖服务,包括托管服务及其依赖的服务都是通过该方法进行注册的。
    • UseServiceProviderFactory:重写用于创建服务提供程序的工厂,即利用该方法可以实现对第三方依赖注入框架的整合。
    • ConfigureContainer:该方法用于进一步对依赖注入容器进行配置,例如通过 autofac 注册整个程序集符合约定的服务类型。

    .NET Core 默认使用 DefaultServiceProviderFactory(实现 IServiceProviderFactory<TContainerBuilder>)类型来提供 IServiceProvider(DI 容器),IHostBuilder 接口提供了对应的扩展方法来对其进行注册,如下代码所示:

    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);
            });
        }
    }

    2.3 创建主机

    • Build:该方法用于创建主机(IHost)对象。

    3.实现解析

    3.1 设计模型

    Hosting 系统涉及的主要核心对象关系如下图所示:

    IHostBuilder 用于创建 IHost,IHostBuilder 的默认实现为 HostBuilder。IHost 用于托管 IHostedService,IHost 的默认实现为 Host,Host 依赖于 IHostApplicationLifetime 和 IHostLifetime 接口,前者可以通过注册回调得到应用程序启动和关闭前后的通知,默认实现为 ApplicationLifetime,后者主要用于实现控制主机何时启动和何时停止,默认实现为 ConsoleLifetime。

    3.2 Host

    在详细介绍 Host 之前,需要先了解与之相关的三个类型。

    3.2.1 ApplicationLifeTime

    ApplicationLifeTime 实现了 IApplicationLifeTime 接口,接口定义的三个属性(ApplicationStarted、ApplicationStopping、ApplicationStopped)是用于注册应用(Host)启动和停止事件处理程序方法的取消令牌。将该类型注入任何类可以得到应用被启动和关闭前后的通知。ApplicationLifeTime 类型的定义如下:

    public class ApplicationLifetime : IHostApplicationLifetime
    {
        private readonly CancellationTokenSource _startedSource = new CancellationTokenSource();
        private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource();
        private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource();
        private readonly ILogger<ApplicationLifetime> _logger;
    
        public ApplicationLifetime(ILogger<ApplicationLifetime> logger)
        {
            _logger = logger;
        }
    
        public CancellationToken ApplicationStarted => _startedSource.Token;
        public CancellationToken ApplicationStopping => _stoppingSource.Token;
        public CancellationToken ApplicationStopped => _stoppedSource.Token;
    
        public void StopApplication()
        {
            lock (_stoppingSource)
            {
                ExecuteHandlers(_stoppingSource);
            }
        }
    
        public void NotifyStarted()
        {
            ExecuteHandlers(_startedSource);
        }
    
        public void NotifyStopped()
        {
            ExecuteHandlers(_stoppedSource);
        }
    
        private void ExecuteHandlers(CancellationTokenSource cancel)
        {
            // Noop if this is already cancelled
            if (cancel.IsCancellationRequested)
            {
                return;
            }
            // Run the cancellation token callbacks
            cancel.Cancel(throwOnFirstException: false);
        }
    }
    View Code

    3.2.2 HostOptions

    HostOptions 仅包含一个 ShutdownTimeOut 属性,该属性用于表示关闭主机时的超时限制。HostOptions 类的定义如下:

    public class HostOptions
    {
        /// <summary>
        /// The default timeout for <see cref="IHost.StopAsync(System.Threading.CancellationToken)"/>.
        /// </summary>
        public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);
    }

    3.2.3 IHostLifeTime

    IHostLifetime 接口主要用于实现控制主机何时启动和何时停止。当 Host 启动时,将调用 IHostLifetime 接口的 WaitForStartAsync 方法。当 Host 关闭时,将调用 IHostLifetime 接口的 StopAsync 方法。IHostLifeTime 接口的定义如下:

    public interface IHostLifetime
    {
        Task WaitForStartAsync(CancellationToken cancellationToken);
        Task StopAsync(CancellationToken cancellationToken);
    }

    IHostLifetime 服务默认的注册类型为 ConsoleLifetime,定义如下:

    /// <summary>
    /// Listens for Ctrl+C or SIGTERM and initiates shutdown.
    /// </summary>
    public class ConsoleLifetime : IHostLifetime, IDisposable
    {
        private readonly ManualResetEvent _shutdownBlock = new ManualResetEvent(false);
        private CancellationTokenRegistration _applicationStartedRegistration;
        private CancellationTokenRegistration _applicationStoppingRegistration;
    
        public ConsoleLifetime(IOptions<ConsoleLifetimeOptions> options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions<HostOptions> hostOptions)
            : this(options, environment, applicationLifetime, hostOptions, NullLoggerFactory.Instance) { }
    
        public ConsoleLifetime(IOptions<ConsoleLifetimeOptions> options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions<HostOptions> hostOptions, ILoggerFactory loggerFactory)
        {
            Options = options?.Value ?? throw new ArgumentNullException(nameof(options));
            Environment = environment ?? throw new ArgumentNullException(nameof(environment));
            ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
            HostOptions = hostOptions?.Value ?? throw new ArgumentNullException(nameof(hostOptions));
            Logger = loggerFactory.CreateLogger("Microsoft.Hosting.Lifetime");
        }
    
        private ConsoleLifetimeOptions Options { get; }
        private IHostEnvironment Environment { get; }
        private IHostApplicationLifetime ApplicationLifetime { get; }
        private HostOptions HostOptions { get; }
        private ILogger Logger { get; }
    
        public Task WaitForStartAsync(CancellationToken cancellationToken)
        {
            if (!Options.SuppressStatusMessages)
            {
                _applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state =>
                {
                    ((ConsoleLifetime)state).OnApplicationStarted();
                }, this);
                _applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state =>
                {
                    ((ConsoleLifetime)state).OnApplicationStopping();
                }, this);
            }
    
            AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
            Console.CancelKeyPress += OnCancelKeyPress;
            // Console applications start immediately.
            return Task.CompletedTask;
        }
    
        private void OnApplicationStarted()
        {
            Logger.LogInformation("Application started. Press Ctrl+C to shut down.");
            Logger.LogInformation("Hosting environment: {envName}", Environment.EnvironmentName);
            Logger.LogInformation("Content root path: {contentRoot}", Environment.ContentRootPath);
        }
    
        private void OnApplicationStopping()
        {
            Logger.LogInformation("Application is shutting down...");
        }
    
        private void OnProcessExit(object sender, EventArgs e)
        {
            ApplicationLifetime.StopApplication();
            if(!_shutdownBlock.WaitOne(HostOptions.ShutdownTimeout))
            {
                Logger.LogInformation("Waiting for the host to be disposed. Ensure all 'IHost' instances are wrapped in 'using' blocks.");
            }
            _shutdownBlock.WaitOne();
            // On Linux if the shutdown is triggered by SIGTERM then that's signaled with the 143 exit code.
            // Suppress that since we shut down gracefully. https://github.com/aspnet/AspNetCore/issues/6526
            System.Environment.ExitCode = 0;
        }
    
        private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            e.Cancel = true;
            ApplicationLifetime.StopApplication();
        }
    
        public Task StopAsync(CancellationToken cancellationToken)
        {
            // There's nothing to do here
            return Task.CompletedTask;
        }
    
        public void Dispose()
        {
            _shutdownBlock.Set();
    
            AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
            Console.CancelKeyPress -= OnCancelKeyPress;
    
            _applicationStartedRegistration.Dispose();
            _applicationStoppingRegistration.Dispose();
        }
    }
    View Code

    3.2.4 Host

    在对上面的几个类型有了基本的了解之后,下面详细讲解 Host 类型,Host 是 IHost 接口的默认实现,定义如下:

    internal class Host : IHost, IAsyncDisposable
    {
        private readonly ILogger<Host> _logger;
        private readonly IHostLifetime _hostLifetime;
        private readonly ApplicationLifetime _applicationLifetime;
        private readonly HostOptions _options;
        private IEnumerable<IHostedService> _hostedServices;
    
        public Host(IServiceProvider services, IHostApplicationLifetime applicationLifetime, ILogger<Host> logger, IHostLifetime hostLifetime, IOptions<HostOptions> options)
        {
            Services = services ?? throw new ArgumentNullException(nameof(services));
            _applicationLifetime = (applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime))) as ApplicationLifetime;
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _hostLifetime = hostLifetime ?? throw new ArgumentNullException(nameof(hostLifetime));
            _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
        }
    
        public IServiceProvider Services { get; }
    }

    Host 类型的构造函数定义了一系列的依赖服务,这些服务都是通过 DI 容器注入进来的(在 IHostBuilder 创建 IHost 时),包括本身作为依赖注入容器的 IServiceProvider 对象(Services 属性,前面已介绍过),托管服务的依赖解析正是通过该对象来实现的。

    Host 的 StartAsync 方法,如下代码所示:

    public async Task StartAsync(CancellationToken cancellationToken = default)
    {
        _logger.Starting();
    
        using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping);
        var combinedCancellationToken = combinedCancellationTokenSource.Token;
    
        await _hostLifetime.WaitForStartAsync(combinedCancellationToken);
    
        combinedCancellationToken.ThrowIfCancellationRequested();
        _hostedServices = Services.GetService<IEnumerable<IHostedService>>();
    
        foreach (var hostedService in _hostedServices)
        {
            // Fire IHostedService.Start
            await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
        }
        // Fire IHostApplicationLifetime.Started
        _applicationLifetime?.NotifyStarted();
    
        _logger.Started();
    }

    当 Host 调用其 StartAsync 方法启动主机时,首先调用 IHostLifetime 接口的 WaitForStartAsync 方法,如果注册的 IHostLifetime 服务类型为 ConsoleLifetime,它会注册控制台的按键事件,当触发 Ctrl+C 或 SIGTERM 事件时,它会调用 IHostApplication 接口的  StopApplication 方法,这将触发所有使用 ApplicationStopping 取消令牌注册的回调(发出应用即将关闭的通知),与此同时,它会输出如下图所示的 3 条控制台日志。接着 Host 会利用依赖注入容器(Services 属性)获取所有注册的托管服务(托管服务依赖的服务也是通过容器解析注入而来),并依次调用它们的 StartAsync 方法,当所有的托管服务被启动后,Host 会调用 ApplicationLifetime 对象的 NotifyStarted 方法,发出应用程序已启动的通知。

    Host 的启动时序如下图所示:

    Host 的 StopAsync 方法,如下代码所示:

    public async Task StopAsync(CancellationToken cancellationToken = default)
    {
        _logger.Stopping();
    
        using (var cts = new CancellationTokenSource(_options.ShutdownTimeout))
        using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
        {
            var token = linkedCts.Token;
            // Trigger IHostApplicationLifetime.ApplicationStopping
            _applicationLifetime?.StopApplication();
    
            IList<Exception> exceptions = new List<Exception>();
            if (_hostedServices != null) // Started?
            {
                foreach (var hostedService in _hostedServices.Reverse())
                {
                    token.ThrowIfCancellationRequested();
                    try
                    {
                        await hostedService.StopAsync(token).ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        exceptions.Add(ex);
                    }
                }
            }
    
            token.ThrowIfCancellationRequested();
            await _hostLifetime.StopAsync(token);
    
            // Fire IHostApplicationLifetime.Stopped
            _applicationLifetime?.NotifyStopped();
    
            if (exceptions.Count > 0)
            {
                var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
                _logger.StoppedWithException(ex);
                throw ex;
            }
        }
    
        _logger.Stopped();
    }

    当 Host 调用其 StopAsync 方法停止主机时,首先调用 ApplicationLifetime 对象的 StopApplication 方法向外发出应用程序即将关闭的通知,随后以与启动托管服务相反的顺序调用其 StopAsync 方法来停止托管服务的运行,当所有的托管服务被停止后,Host 会调用 IHostLifetime 对象的 StopAsync 方法(如果注册的类型为 ConsoleLifetime,则为空操作)和 ApplicationLifetime 对象的 NotifyStopped 方法(发出应用程序已关闭的通知)。在 Host 关闭的过程中,如果时间超过了 HostOptions 中 ShutdownTimeout 定义的超时限制,则整个关闭过程会终止。Host 的停止时序如下图所示:

    3.3 HostBuilder

    3.3.1 加载主机及应用配置

    针对主机和应用的配置的实现代码如下所示,ConfigureHostConfiguration 和 ConfigureAppConfiguration 方法仅仅将传入的配置参数(委托类型)添加到对应的集合( _configureHostConfigActions 和 _configureAppConfigActions 字段)中。

    public class HostBuilder : IHostBuilder
    {
        private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();
        private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();
        
        public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
        {
            _configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
            return this;
        }
    
        public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
        {
            _configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
            return this;
        }
    }

    3.3.2 配置依赖注入框架

    针对依赖注入框架的配置的实现代码如下所示,ConfigureServices 用于注册依赖服务,该方法仅仅将传入的配置参数(委托类型)添加到 _configureServicesActions 集合中。

    public class HostBuilder : IHostBuilder
    {
        private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>();
    
        public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
        {
            _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
            return this;
        }
    }

    在主机的创建过程中,作为依赖注入容器(IServiceProvider)的对象是通过 IServiceProviderFactory<TContainerBuilder> 工厂创建的,利用 UseServiceProviderFactory 方法可以重写用于创建依赖注入容器的工厂,即利用该方法可以实现对第三方依赖注入框架的整合。由于 _serviceProdiverFactory 的初始化值为 ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory()) 对象,如果没有外部代码调用 UseServiceProviderFactory 方法,即代表当前使用的正是 .NET Core 内置的依赖注入框架。

    public class HostBuilder : IHostBuilder
    {
        private IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
        
        public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
        {
            _serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
            return this;
        }
    
        public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
        {
            _serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(() => _hostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory)));
            return this;
        }
    }

    由于 UseServiceProviderFactory 方法注册的是一个泛型的工厂对象,HostBuilder 类会利用一个名为 IServiceFactoryAdapter 的接口来进行适配,该接口仅仅是将 TContainerBuilder 类型转换为 object 类型而已。IServiceFactoryAdapter 接口的定义如下:

    internal interface IServiceFactoryAdapter
    {
        object CreateBuilder(IServiceCollection services);
        IServiceProvider CreateServiceProvider(object containerBuilder);
    }

    ServiceFactoryAdapter<TContainerBuilder> 是 IServiceFactoryAdapter 接口的默认实现类,定义如下:

    internal class ServiceFactoryAdapter<TContainerBuilder> : IServiceFactoryAdapter
    {
        private IServiceProviderFactory<TContainerBuilder> _serviceProviderFactory;
        private readonly Func<HostBuilderContext> _contextResolver;
        private Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> _factoryResolver;
    
        public ServiceFactoryAdapter(IServiceProviderFactory<TContainerBuilder> serviceProviderFactory)
        {
            _serviceProviderFactory = serviceProviderFactory ?? throw new ArgumentNullException(nameof(serviceProviderFactory));
        }
    
        public ServiceFactoryAdapter(Func<HostBuilderContext> contextResolver, Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factoryResolver)
        {
            _contextResolver = contextResolver ?? throw new ArgumentNullException(nameof(contextResolver));
            _factoryResolver = factoryResolver ?? throw new ArgumentNullException(nameof(factoryResolver));
        }
    
        public object CreateBuilder(IServiceCollection services)
        {
            if (_serviceProviderFactory == null)
            {
                _serviceProviderFactory = _factoryResolver(_contextResolver());
                if (_serviceProviderFactory == null)
                {
                    throw new InvalidOperationException("The resolver returned a null IServiceProviderFactory");
                }
            }
            return _serviceProviderFactory.CreateBuilder(services);
        }
    
        public IServiceProvider CreateServiceProvider(object containerBuilder)
        {
            if (_serviceProviderFactory == null)
            {
                throw new InvalidOperationException("CreateBuilder must be called before CreateServiceProvider");
            }
    
            return _serviceProviderFactory.CreateServiceProvider((TContainerBuilder)containerBuilder);
        }
    }

    在利用 UseServiceProviderFactory 方法重写了默认的工厂后,我们还可以通过 ConfigureContainer 方法对 TContainerBuilder(一般作为服务容器,例如 IServiceCollection 对象)对象进行进一步的配置。该方法也是将配置参数(委托类型)添加到对应的集合(  _configureContainerActions )中。

    public class HostBuilder : IHostBuilder
    {
        private List<IConfigureContainerAdapter> _configureContainerActions = new List<IConfigureContainerAdapter>();
    public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate) { _configureContainerActions.Add(new ConfigureContainerAdapter<TContainerBuilder>(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)))); return this; } }

    同样的,由于配置委托是一个泛型对象,HostBuilder 类会利用一个名为 IConfigureContainerAdapter 的接口来进行适配,该接口也仅仅是将 TContainerBuilder 类型转换为 object 类型而已。IConfigureContainerAdapter 接口的定义如下:

    internal interface IConfigureContainerAdapter
    {
        void ConfigureContainer(HostBuilderContext hostContext, object containerBuilder);
    }

    ConfigureContainerAdapter<TContainerBuilder> 是 IConfigureContainerAdapter 接口的默认实现类,定义如下:

    internal class ConfigureContainerAdapter<TContainerBuilder> : IConfigureContainerAdapter
    {
        private Action<HostBuilderContext, TContainerBuilder> _action;
    
        public ConfigureContainerAdapter(Action<HostBuilderContext, TContainerBuilder> action)
        {
            _action = action ?? throw new ArgumentNullException(nameof(action));
        }
    
        public void ConfigureContainer(HostBuilderContext hostContext, object containerBuilder)
        {
            _action(hostContext, (TContainerBuilder)containerBuilder);
        }
    }

    3.3.3 创建主机

    对于主机的创建,是通过调用 HostBuilder 的 Build 方法实现的。前面讲到 Host 的构造函数中的对象(服务)是通过依赖注入容器注入进来的,所以针对 Host 的创建,并非是直接调用其构造函数(new)实现的,而是通过依赖注入容器获取的。所以在获取 Host 对象之前,需要注册必要的配置和服务。如下代码所示,Host 的创建过程主要分 3个 步骤:

    1. 创建主机配置及 HostBuilderContext 对象
    2. 创建应用配置
    3. 注册依赖服务并创建依赖注入容器
    private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();
    private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();
    private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>();
    private List<IConfigureContainerAdapter> _configureContainerActions = new List<IConfigureContainerAdapter>();
    private IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
    
    private bool _hostBuilt;
    private IConfiguration _hostConfiguration;
    private IConfiguration _appConfiguration;
    private HostBuilderContext _hostBuilderContext;
    private HostingEnvironment _hostingEnvironment;
    private IServiceProvider _appServices;
    
    /// <summary>
    /// A central location for sharing state between components during the host building process.
    /// </summary>
    public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
            
    public IHost Build()
    {
        if (_hostBuilt)
        {
            throw new InvalidOperationException("Build can only be called once.");
        }
        _hostBuilt = true;
    
        BuildHostConfiguration();
        CreateHostingEnvironment();
        CreateHostBuilderContext();
        BuildAppConfiguration();
        CreateServiceProvider();
    
        return _appServices.GetRequiredService<IHost>();
    }

    1.创建主机配置及 HostBuilderContext 对象,如下代码所示:

    private void BuildHostConfiguration()
    {
        var configBuilder = new ConfigurationBuilder()
            .AddInMemoryCollection(); // Make sure there's some default storage since there are no default providers
    
        foreach (var buildAction in _configureHostConfigActions)
        {
            buildAction(configBuilder);
        }
        _hostConfiguration = configBuilder.Build();
    }
    
    private void CreateHostingEnvironment()
    {
        _hostingEnvironment = new HostingEnvironment()
        {
            ApplicationName = _hostConfiguration[HostDefaults.ApplicationKey],
            EnvironmentName = _hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production,
            ContentRootPath = ResolveContentRootPath(_hostConfiguration[HostDefaults.ContentRootKey], AppContext.BaseDirectory),
        };
    
        if (string.IsNullOrEmpty(_hostingEnvironment.ApplicationName))
        {
            // Note GetEntryAssembly returns null for the net4x console test runner.
            _hostingEnvironment.ApplicationName = Assembly.GetEntryAssembly()?.GetName().Name;
        }
    
        _hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath);
    }
    
    private string ResolveContentRootPath(string contentRootPath, string basePath)
    {
        if (string.IsNullOrEmpty(contentRootPath))
        {
            return basePath;
        }
        if (Path.IsPathRooted(contentRootPath))
        {
            return contentRootPath;
        }
        return Path.Combine(Path.GetFullPath(basePath), contentRootPath);
    }
    
    private void CreateHostBuilderContext()
    {
        _hostBuilderContext = new HostBuilderContext(Properties)
        {
            HostingEnvironment = _hostingEnvironment,
            Configuration = _hostConfiguration
        };
    }

    BuildHostConfiguration 方法首先创建了一个 ConfigurationBuilder 对象,接着使用该对象作为参数调用使用 ConfigureHostConfiguration 方法注册的所有主机配置委托(Action<IConfigurationBuilder>)并创建出一个主机配置对象( _hostConfiguration)。通过主机配置对象,HostBuilder 创建了一个 HostBuilderContext 上下文对象,并从主机配置中查找对应的配置项用于初始化主机环境。

    2.创建应用配置,如下代码所示:

    private void BuildAppConfiguration()
    {
        var configBuilder = new ConfigurationBuilder()
            .SetBasePath(_hostingEnvironment.ContentRootPath)
            .AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);
    
        foreach (var buildAction in _configureAppConfigActions)
        {
            buildAction(_hostBuilderContext, configBuilder);
        }
        _appConfiguration = configBuilder.Build();
        _hostBuilderContext.Configuration = _appConfiguration;
    }

    BuildAppConfiguration 方法首先创建了一个 ConfigurationBuilder 对象,设置主机环境的内容文件根目录路径作为配置文件的基础路径,并合并上一步骤创建的主机配置,接着使用该对象和 HostBuilderContext 对象作为参数调用使用 ConfigureAppConfiguration 方法注册的所有应用配置委托(Action<HostBuilderContext, IConfigurationBuilder>)并创建出一个应用配置对象( _appConfiguration),最后将该应用配置重新赋值给 HostBuilderContext 对象的 Configuration 属性。

    3.注册依赖服务并创建依赖注入容器,如下代码所示:

    private void CreateServiceProvider()
    {
        var services = new ServiceCollection();
        services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
        services.AddSingleton(_hostBuilderContext);
        // register configuration as factory to make it dispose with the service provider
        services.AddSingleton(_ => _appConfiguration);
        services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();
        services.AddSingleton<IHostLifetime, ConsoleLifetime>();
        services.AddSingleton<IHost, Internal.Host>();
        services.AddOptions();
        services.AddLogging();
    
        foreach (var configureServicesAction in _configureServicesActions)
        {
            configureServicesAction(_hostBuilderContext, services);
        }
    
        var containerBuilder = _serviceProviderFactory.CreateBuilder(services);
        foreach (var containerAction in _configureContainerActions)
        {
            containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
        }
    
        _appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);
    }

    CreateServiceProvider 方法首先创建了一个服务容器(IServiceCollection)对象,并注册了一系列的默认服务,包括 IHostApplicationLifetime/ApplicationLifetime、IHostLifetime/ConsoleLifetime 以及 IHost/Host 等服务,接着使用该对象和 HostBuilderContext 对象作为参数调用使用 ConfigureServices 方法注册的所有依赖服务注册委托(Action<HostBuilderContext, IServiceCollection>)。完成对依赖服务的注册后,IServiceProviderFactory 对象根据注册的服务创建出一个 ContainerBuilder 对象(object 类型),接着使用该对象和 HostBuilderContext 对象作为参数调用使用 ConfigureContainer 方法注册的所有委托(Action<HostBuilderContext, TContainerBuilder>)来进一步注册所需要的依赖服务,最后通过调用 IServiceProviderFactory 对象的 CreateServiceProvider 方法创建出一个代表依赖注入容器的 IServiceProvider 对象,Build 方法正是使用该对象来创建 Host 对象的。

    4.初始化 HostBuilder 并启动主机

    4.1 CreateDefaultBuilder 方法

    静态的 Host 类为我们提供了一个 CreateDefaultBuilder 方法来创建 HostBuilder 对象,该方法会为我们做一些必要的配置工作,如设置内容文件根目录为 Directory.GetCurrentDirectory() 方法返回的路径、加载环境变量和命令行参数、加载 application.json 和 application.{env.EnvironmentName}.json 配置文件、注册日志记录提供程序等。具体实现代码如下所示:

    public static class Host
    {
        public static IHostBuilder CreateDefaultBuilder(string[] args)
        {
            var builder = new HostBuilder();
    
            builder.UseContentRoot(Directory.GetCurrentDirectory());
            builder.ConfigureHostConfiguration(config =>
            {
                config.AddEnvironmentVariables(prefix: "DOTNET_");
                if (args != null)
                {
                    config.AddCommandLine(args);
                }
            });
    
            builder.ConfigureAppConfiguration((hostingContext, config) =>
            {
                var env = hostingContext.HostingEnvironment;
    
                config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
    
                if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
                {
                    var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
                    if (appAssembly != null)
                    {
                        config.AddUserSecrets(appAssembly, optional: true);
                    }
                }
    
                config.AddEnvironmentVariables();
    
                if (args != null)
                {
                    config.AddCommandLine(args);
                }
            })
            .ConfigureLogging((hostingContext, logging) =>
            {
                var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
    
                // IMPORTANT: This needs to be added *before* configuration is loaded, this lets
                // the defaults be overridden by the configuration.
                if (isWindows)
                {
                    // Default the EventLogLoggerProvider to warning or above
                    logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
                }
    
                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                logging.AddConsole();
                logging.AddDebug();
                logging.AddEventSourceLogger();
    
                if (isWindows)
                {
                    // Add the EventLogLoggerProvider on windows machines
                    logging.AddEventLog();
                }
            })
            .UseDefaultServiceProvider((context, options) =>
            {
                var isDevelopment = context.HostingEnvironment.IsDevelopment();
                options.ValidateScopes = isDevelopment;
                options.ValidateOnBuild = isDevelopment;
            });
    
            return builder;
        }
    }

    从实现代码来看,默认加载的配置优先级为:命令行参数 > 应用配置(通过application.json等配置文件) > 硬编码(通过 UseEnvironment、UseContentRoot 等扩展方法) > 环境变量,即若加载相同 Key 值的配置项,则优先级高的会覆盖掉优先级较低的。 

    4.2 Run 扩展方法

    Host(主机)对象为我们提供了一个 Run 扩展方法,该方法内部会调用 Host 对象的 StartAsync 方法并持续等待,直到接受到来自 IHostApplicationLifetime 服务的 ApplicationStopping(关闭应用)通知,在接收到该通知后,Host 对象调用其 StopAsync 方法关闭主机,此时 Run 方法返回并结束应用程序。Run 方法的具体实现代码如下所示:

    public static void Run(this IHost host)
    {
        host.RunAsync().GetAwaiter().GetResult();
    }
    
    public static async Task RunAsync(this IHost host, CancellationToken token = default)
    {
        try
        {
            await host.StartAsync(token);
            await host.WaitForShutdownAsync(token);
        }
        finally
        {
            if (host is IAsyncDisposable asyncDisposable)
            {
                await asyncDisposable.DisposeAsync();
            }
            else
            {
                host.Dispose();
            }
        }
    }

    Run 方法实际调用了异步的 RunAsync 方法,该方法首先调用 Host 对象的 StartAsync 方法,接着调用 Host 对象的 WaitForShutdownAsync 扩展方法,该方法用于等待 ApplicationStopping(关闭应用)的通知。WaitForShutdownAsync 方法的具体实现代码如下所示:

    public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default)
    {
        var applicationLifetime = host.Services.GetService<IHostApplicationLifetime>();
    
        token.Register(state =>
        {
            ((IHostApplicationLifetime)state).StopApplication();
        }, applicationLifetime);
    
        var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
        applicationLifetime.ApplicationStopping.Register(obj =>
        {
            var tcs = (TaskCompletionSource<object>)obj;
            tcs.TrySetResult(null);
        }, waitForStop);
    
        await waitForStop.Task;
    
        // Host will use its default ShutdownTimeout if none is specified.
        await host.StopAsync();
    }

    整个构建、启动与关闭主机的时序,如下图所示:

  • 相关阅读:
    获取指定路径下的文件夹及文件名称
    Unity3D两种方式实现游戏视频播放
    Unity Steam_VR Camera
    Unity Steam_VR 开发工具插件 VRTK自带案例分析(第一部分)
    Unity Steam_VR 开发工具插件 VRTK自带案例分析(第二部分)
    weblogic安装注意事项_linux
    从英文变形规则计算到Restful Api设计
    如何正确使用Cocoapods
    clang -rewrite-objc的使用点滴
    kubernetes听云实战发布版
  • 原文地址:https://www.cnblogs.com/chenyuxin/p/Hosting.html
Copyright © 2011-2022 走看看