首页 科技内容详情
明白ASP.NET Core - [04] Host

明白ASP.NET Core - [04] Host

分类:科技

网址:

SEO查询: 爱站网 站长工具

点击直达

ug610登陆www.ugbet.us)开放环球UG代理登录网址、会员登录网址、环球UG会员注册、环球UG代理开户申请、环球UG电脑客户端、环球UG手机版下载等业务。

注:本文隶属于《明白ASP.NET Core》系列文章,请查看置顶博客或点击此处查看全文目录

本文会涉及部门 Host 相关的源码,并会附上 github 源码地址,不外为了降低篇幅,我会删除一些不涉及的代码。

为了利便,照样建议你将源码(.net5)runtime 和 aspnetcore 下载下来,通过VS等工具阅读

请耐心阅读!

Generic Host & WebHost

在.NET Core 2.x时,ASP.NET Core 默认使用的是WebHost

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

而到了.NET Core 3.x,ASP.NET Core 默认选择使用Generic Host

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

那么,为什么.NET团队要将Web主机(Web Host)替换为通用主机(Generic Host)呢?

参考 What is the difference between Host and WebHost class in asp.net core

Generic Host在.NET Core 2.1就已经存在了,而且它就是根据.NET Core未来版本的通用尺度来实现的。不外由于那时的Generic Host只能用于非HTTP事情负载,以是.NET Core 2.x仍然使用的是 Web Host。不外到了.NET Core 3.x,Generic Host已经可以同时支持HTTP和非HTTP事情负载了。

为什么要使用Generic Host呢?那是由于Web Host与HTTP请求慎密关联,且用于Web应用。然而,随着微服务和Docker的泛起,.NET团队以为需要一个加倍通用的主机,不仅能够服务于Web应用,还能服务于控制台等其他类型的应用。以是就实现了Generic Host

在我们的ASP.NET Core应用中,需要确立一个Generic Host,并通过ConfigureWebHostDefaults等扩展方式针对Web Host举行设置。

以是,我们应该在所有类型的应用中始终使用通用主机

因此,接下来咱们就聊一下通用主机。

Generic Host——通用主机

先上两张Host的启动流程图:

请人人就着上面这张两图食用以下内容。

ConfigureXXX

在深入之前,人人要先领会一下ConfigureHostConfigurationConfigureAppConfigurationConfigureServices等方式到底做了什么。实在,很简朴,就是将委托暂存到了一个暂且变量里。

public class HostBuilder : IHostBuilder
{
    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>>();

    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;
    }
        
    public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
    {
        _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
        return this;
    }
}

Host.CreateDefaultBuilder(args)

  • 源码请戳CreateDefaultBuilder
public static class Host
{
    public static IHostBuilder CreateDefaultBuilder(string[] args)
    {
        var builder = new HostBuilder();
    
        // 将 Content Root(项目根目录)设置为 Directory.GetCurrentDirectory (当前事情目录)
        builder.UseContentRoot(Directory.GetCurrentDirectory());
        builder.ConfigureHostConfiguration(config =>
        {
            // 添加以 DOTNET_ 为前缀的环境变量(会将前缀删除作为环境变量的Key)
            config.AddEnvironmentVariables(prefix: "DOTNET_");
            if (args != null)
            {
                // 添加下令行参数 args
                config.AddCommandLine(args);
            }
        });
    
        builder.ConfigureAppConfiguration((hostingContext, config) =>
        {
            IHostEnvironment env = hostingContext.HostingEnvironment;
    
            // 默认当设置发生更改时,重载设置
            bool reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true);
    
            // appsettings.json、appsettings.{Environment}.json
            config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
                  .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
    
            // 启用 User Secrets(仅当运行在 Development 环境时)
            if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
            {
                var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
                if (appAssembly != null)
                {
                    config.AddUserSecrets(appAssembly, optional: true);
                }
            }
    
            // 添加环境变量(未限制前缀)
            // 目的是当应用(App)设置加载完毕后(注重是加载完毕后),允许读取所有环境变量,且优先级更高
            // 即若存在多个同名的环境变量,不带前缀的比带前缀的优先级更高
            config.AddEnvironmentVariables();
    
            if (args != null)
            {
                // 添加下令行参数 args
                config.AddCommandLine(args);
            }
        })
        .ConfigureLogging((hostingContext, logging) =>
        {
            bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
    
            if (isWindows)
            {
                logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
            }
    
            // 添加 Logging 设置
            logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
            
            logging.AddConsole();
            logging.AddDebug();
            logging.AddEventSourceLogger();
    
            if (isWindows)
            {
                // 在Windows平台上,添加 EventLogLoggerProvider
                logging.AddEventLog();
            }
    
            logging.Configure(options =>
            {
                options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
                                                    | ActivityTrackingOptions.TraceId
                                                    | ActivityTrackingOptions.ParentId;
            });
    
        })
        .UseDefaultServiceProvider((context, options) =>
        {
            // 启用局限验证 scope validation 和依赖关系验证 dependency validation(仅当运行在 Development 环境时)
            bool isDevelopment = context.HostingEnvironment.IsDevelopment();
            options.ValidateScopes = isDevelopment;
            options.ValidateOnBuild = isDevelopment;
        });
    
        return builder;
    }
}

ConfigureWebHostDefaults

  • 源码请戳ConfigureWebHostDefaults、ConfigureWebHost、GenericWebHostBuilder、WebHost.ConfigureWebDefaults、UseStartup、GenericWebHostBuilder.UseStartup、GenericWebHostService
public static class GenericHostBuilderExtensions
{
    public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)
    {
        return builder.ConfigureWebHost(webHostBuilder =>
        {
            WebHost.ConfigureWebDefaults(webHostBuilder);
    
            // 执行 UseStartup 等
            configure(webHostBuilder);
        });
    }
}

public static class GenericHostWebHostBuilderExtensions
{
    public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure)
    {
        return builder.ConfigureWebHost(configure, _ => { });
    }
    
    public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureWebHostBuilder)
    {
        var webHostBuilderOptions = new WebHostBuilderOptions();
        configureWebHostBuilder(webHostBuilderOptions);
        
        // 重点1: GenericWebHostBuilder
        var webhostBuilder = new GenericWebHostBuilder(builder, webHostBuilderOptions);
        configure(webhostBuilder);
        
        // 重点2:GenericWebHostService
        builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
        return builder;
    }
}

上面这段代码重点有两个:

  • 一个是GenericWebHostBuilder这个类,记着它,ConfigureWebHostDefaults委托中的webBuilder参数就是它!
  • 另一个是GenericWebHostService

下面,我们先看一下GenericWebHostBuilder的组织函数:

internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider
{
    public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options)
    {
        _builder = builder;
        var configBuilder = new ConfigurationBuilder()
            .AddInMemoryCollection();
    
        if (!options.SuppressEnvironmentConfiguration)
        {
            // 添加以 ASPNETCORE_ 为前缀的环境变量(会将前缀删除作为环境变量的Key)
            configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_");
        }
    
        _config = configBuilder.Build();
    
        _builder.ConfigureHostConfiguration(config =>
        {
            // 添加到主机(Host)设置
            config.AddConfiguration(_config);
            
            // 执行 HostingStartups,详见下方的 ExecuteHostingStartups 方式
            ExecuteHostingStartups();
        });
    
        _builder.ConfigureAppConfiguration((context, configurationBuilder) =>
        {
            // 在 ExecuteHostingStartups 方式中,该字段通常会被初始化
            if (_hostingStartupWebHostBuilder != null)
            {
                var webhostContext = GetWebHostBuilderContext(context);
                // 加载 HostingStartups 中添加的应用(App)设置
                _hostingStartupWebHostBuilder.ConfigureAppConfiguration(webhostContext, configurationBuilder);
            }
        });
    
        _builder.ConfigureServices((context, services) =>
        {
            var webhostContext = GetWebHostBuilderContext(context);
            var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
    
            // 注册 IWebHostEnvironment
            services.AddSingleton(webhostContext.HostingEnvironment);
            services.AddSingleton((AspNetCore.Hosting.IHostingEnvironment)webhostContext.HostingEnvironment);
            services.AddSingleton<IApplicationLifetime, GenericWebHostApplicationLifetime>();
    
            services.Configure<GenericWebHostServiceOptions>(options =>
            {
                options.WebHostOptions = webHostOptions;
                options.HostingStartupExceptions = _hostingStartupErrors;
            });
    
            var listener = new DiagnosticListener("Microsoft.AspNetCore");
            services.TryAddSingleton<DiagnosticListener>(listener);
            services.TryAddSingleton<DiagnosticSource>(listener);
    
            services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
            services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
            services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
    
            // 注册 IHostingStartup 中设置的服务
            _hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services);
    
            if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
            {
                try
                {
                    var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName);
                    UseStartup(startupType, context, services);
                }
                catch (Exception ex) when (webHostOptions.CaptureStartupErrors)
                {
                    var capture = ExceptionDispatchInfo.Capture(ex);
    
                    services.Configure<GenericWebHostServiceOptions>(options =>
                    {
                        options.ConfigureApplication = app =>
                        {
                            capture.Throw();
                        };
                    });
                }
            }
        });
    }
    
    private void ExecuteHostingStartups()
    {
        var webHostOptions = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);
    
        if (webHostOptions.PreventHostingStartup)
        {
            return;
        }
    
        var exceptions = new List<Exception>();
        // 注重这里对 _hostingStartupWebHostBuilder 举行了初始化
        _hostingStartupWebHostBuilder = new HostingStartupWebHostBuilder(this);
    
        // 从当前途序集和环境变量`ASPNETCORE_HOSTINGSTARTUPASSEMBLIES`设置的程序集列表(清扫`ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES`中设置的程序集列表)中寻找特征`HostingStartupAttribute`,
        // 并通过反射的方式确立特征所标识的`IHostingStartup`实现的实例,并挪用其`Configure`方式。
        foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase))
        {
            try
            {
                var assembly = Assembly.Load(new AssemblyName(assemblyName));
    
                foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
                {
                    var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
                    hostingStartup.Configure(_hostingStartupWebHostBuilder);
                }
            }
            catch (Exception ex)
            {
                exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex));
            }
        }
    
        if (exceptions.Count > 0)
        {
            _hostingStartupErrors = new AggregateException(exceptions);
        }
    }
}

接着来看WebHost.ConfigureWebDefaults

public static class WebHost
{
    internal static void ConfigureWebDefaults(IWebHostBuilder builder)
    {
        builder.ConfigureAppConfiguration((ctx, cb) =>
        {
            if (ctx.HostingEnvironment.IsDevelopment())
            {
                StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
            }
        });
        // 将 Kestrel 服务器设置为 Web 服务器,并添加设置
        builder.UseKestrel((builderContext, options) =>
        {
            options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true);
        })
        .ConfigureServices((hostingContext, services) =>
        {
            // 设置主机过滤中央件(Host Filtering)
            services.PostConfigure<HostFilteringOptions>(options =>
            {
                if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
                {
                    var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                    options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
                }
            });
    
            services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
                        new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));
    
            services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
    
            // 当环境变量 ASPNETCORE_FORWARDEDHEADERS_ENABLED 为 true 时,添加转讨论中央件(Forwarded Headers)
            if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase))
            {
                services.Configure<ForwardedHeadersOptions>(options =>
                {
                    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
                    options.KnownNetworks.Clear();
                    options.KnownProxies.Clear();
                });
    
                services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>();
            }
    
            services.AddRouting();
        })
        // 启用IIS集成
        .UseIIS()
        .UseIISIntegration();
    }
}

我们通常会在ConfigureWebHostDefaults扩展方式的委托中挪用UseStartup来指定Startup类,下面我们就来看一下UseStartup到底做了什么:将Startup.ConfigureServices中要注册的服务添加到ConfigureServices的委托中

public static class WebHostBuilderExtensions
{
    public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)]TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
    {
        return hostBuilder.UseStartup(typeof(TStartup));
    }
    
    public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, [DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
    {
        // ...删除了一些代码
    
        // 会进入该条件分支
        // 不知道为什么进入该分支?上面让你切记的 GenericWebHostBuilder 还记得吗?快去看看它实现了哪些接口
        if (hostBuilder is ISupportsStartup supportsStartup)
        {
            return supportsStartup.UseStartup(startupType);
        }
    
        // ...删除了一些代码
    }
}

internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider
{
    public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
    {
        // 可以看到,虽然 UseStartup 可以挪用多次,然则只有最后一次才有用
        _startupObject = startupType;
    
        // 将 Startup.ConfigureServices 中要注册的服务添加进来
        // 好了,暂时看到这里就ok了
        _builder.ConfigureServices((context, services) =>
        {
            if (object.ReferenceEquals(_startupObject, startupType))
            {
                UseStartup(startupType, context, services);
            }
        });
    
        return this;
    }
}

最后,看一下上面提到的第二个重点GenericWebHostService:用于后续Run方式时执行Configure(包罗StartupFilters.ConfigureStartup.Configure等)

internal class GenericWebHostService : IHostedService
{
    // 组织函数注入
    public GenericWebHostServiceOptions Options { get; }
    // 组织函数注入
    public IEnumerable<IStartupFilter> StartupFilters { get; }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        // ...删除了一些代码

        RequestDelegate application = null;

        try
        {
            // 这里取到了 Startup.Configure
            // 可能你不知道为什么这里可以取到,别着急,文章后面会为你注释的
            Action<IApplicationBuilder> configure = Options.ConfigureApplication;

            // 要求 Startup 必须包罗 Configure 方式,或必须挪用 IWebHostBuilder.Configure
            if (configure == null)
            {
                throw new InvalidOperationException($"No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration.");
            }

            var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features);
            
            // 注重:这里来执行 StartupFilters.Configure 与 Startup.Configure 
            // 将 Startup.Configure 与 StartupFilters.Configure 毗邻成中央件管道
            // 为什么 Reverse?由于要先执行 StartupFilters.Configure,最后才执行 Startup.Configure,
            // 以是用类似链条的方式,从尾巴最先向头部牵手,这样,最终获得的 configure 指向的就是头部
            // 当执行 configure 时,就可以重新部流转到尾巴
            foreach (var filter in StartupFilters.Reverse())
            {
                configure = filter.Configure(configure);
            }

            // 执行 Configure 方式
            configure(builder);

            // Build HTTP 请求管道
            application = builder.Build();
        }
        catch (Exception ex)
        {
            Logger.ApplicationError(ex);

            if (!Options.WebHostOptions.CaptureStartupErrors)
            {
                throw;
            }

            application = BuildErrorPageApplication(ex);
        }

        var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, HttpContextFactory);

        await Server.StartAsync(httpApplication, cancellationToken);

        // ...删除了一些代码
    }
}

Build

  • 源码请戳Build
public class HostBuilder : IHostBuilder
{
    public IHost Build()
    {
        // 加载主机(Host)设置
        BuildHostConfiguration();
        // 实例化 HostingEnvironment
        CreateHostingEnvironment();
        // 实例化 HostBuilderContext
        CreateHostBuilderContext();
        // 加载应用(App)设置
        BuildAppConfiguration();
        // 注册服务并确立 Service Provider
        CreateServiceProvider();
    
        // 天生 IHost 实例并返回
        return _appServices.GetRequiredService<IHost>();
    }
}

BuildHostConfiguration

public class HostBuilder : IHostBuilder
{
    private void BuildHostConfiguration()
    {
        IConfigurationBuilder configBuilder = new ConfigurationBuilder()
            .AddInMemoryCollection(); 
    
        // 加载主机(Host)设置(同时会执行上面所说的 IHostingStartup.Configure)
        foreach (Action<IConfigurationBuilder> buildAction in _configureHostConfigActions)
        {
            buildAction(configBuilder);
        }
        _hostConfiguration = configBuilder.Build();
    }
}

CreateHostingEnvironment

public class HostBuilder : IHostBuilder
{
    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))
        {
            _hostingEnvironment.ApplicationName = Assembly.GetEntryAssembly()?.GetName().Name;
        }
    
        _hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath);
    }
}

CreateHostBuilderContext

public class HostBuilder : IHostBuilder
{
    private void CreateHostBuilderContext()
    {
        _hostBuilderContext = new HostBuilderContext(Properties)
        {
            HostingEnvironment = _hostingEnvironment,
            Configuration = _hostConfiguration
        };
    }
}

BuildAppConfiguration

public class HostBuilder : IHostBuilder
{
    private void BuildAppConfiguration()
    {
        IConfigurationBuilder configBuilder = new ConfigurationBuilder()
            .SetBasePath(_hostingEnvironment.ContentRootPath)
            .AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);
    
        foreach (Action<HostBuilderContext, IConfigurationBuilder> buildAction in _configureAppConfigActions)
        {
            buildAction(_hostBuilderContext, configBuilder);
        }
        _appConfiguration = configBuilder.Build();
        _hostBuilderContext.Configuration = _appConfiguration;
    }
}

CreateServiceProvider

public class HostBuilder : IHostBuilder
{
    private void CreateServiceProvider()
    {
        var services = new ServiceCollection();
        services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
        // 注册 IHostEnvironment
        services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
        // 注册 HostBuilderContext
        services.AddSingleton(_hostBuilderContext);
        // 注册 IConfiguration,以是能在 Startup 中举行组织函数注入
        services.AddSingleton(_ => _appConfiguration);
        services.AddSingleton<IApplicationLifetime>(s => (IApplicationLifetime)s.GetService<IHostApplicationLifetime>());
        services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();
        // 注重这里注册了 IHostLifetime 服务的实例 ConsoleLifetime
        services.AddSingleton<IHostLifetime, ConsoleLifetime>();
        // 注册 IHost 实例
        services.AddSingleton<IHost, Internal.Host>();
        services.AddOptions();
        services.AddLogging();
    
        // 执行 ConfigureServices 方式中的委托举行服务注册
        // 包罗使用扩展方式 ConfigureServices、 Startup.ConfigureServices 等设置的委托
        foreach (Action<HostBuilderContext, IServiceCollection> configureServicesAction in _configureServicesActions)
        {
            configureServicesAction(_hostBuilderContext, services);
        }
    
        object containerBuilder = _serviceProviderFactory.CreateBuilder(services);
    
        // 加载容器设置
        foreach (IConfigureContainerAdapter containerAction in _configureContainerActions)
        {
            containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
        }
    
        // 确立 Service Provider
        _appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);
    
        if (_appServices == null)
        {
            throw new InvalidOperationException($"The IServiceProviderFactory returned a null IServiceProvider.");
        }
    
        _ = _appServices.GetService<IConfiguration>();
    }
}

Run

  • 源码请戳Run、StartAsync
public static class HostingAbstractionsHostExtensions
{
    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).ConfigureAwait(false);
    
            await host.WaitForShutdownAsync(token).ConfigureAwait(false);
        }
        finally
        {
            if (host is IAsyncDisposable asyncDisposable)
            {
                await asyncDisposable.DisposeAsync().ConfigureAwait(false);
            }
            else
            {
                host.Dispose();
            }
    
        }
    }
}

StartAsync

欧博app下载

欢迎进入欧博app下载网站:(www.aLLbetgame.us),欧博app下载网站是欧博官方网站。欧博app下载网站开放欧博注册、欧博代理、欧博电脑客户端、欧博app下载等业务。

internal class Host : IHost, IAsyncDisposable
{
    public async Task StartAsync(CancellationToken cancellationToken = default)
    {
        _logger.Starting();
    
        using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping);
        CancellationToken combinedCancellationToken = combinedCancellationTokenSource.Token;
    
        // _hostLifetime 是在组织函数注入的
        // 还记得吗?在上面的 CreateServiceProvider 方式中,注入了该服务的默认实例 ConsoleLifetime,在下方你可以看到 ConsoleLifetime 的部门实现
        await _hostLifetime.WaitForStartAsync(combinedCancellationToken).ConfigureAwait(false);
    
        combinedCancellationToken.ThrowIfCancellationRequested();
        
        // 这内里就包罗我们上面提到的重点 GenericWebHostService
        _hostedServices = Services.GetService<IEnumerable<IHostedService>>();
    
        foreach (IHostedService hostedService in _hostedServices)
        {
            // 激活 IHostedService.StartAsync
            await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
        }
    
        // 激活 IHostApplicationLifetime.Started
        _applicationLifetime.NotifyStarted();
    
        _logger.Started();
    }
}

public class ConsoleLifetime : IHostLifetime, IDisposable
{
    public Task WaitForStartAsync(CancellationToken cancellationToken)
    {
        // ...删除了一些代码
    
        // 注册了程序退出回调
        AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
        // 注册了 Ctrl + C 回调(这下你知道为啥执行了 Ctrl + C 程序就退出了吧?)
        Console.CancelKeyPress += OnCancelKeyPress;
    
        // 立刻启动 Console applications
        return Task.CompletedTask;
    }
    
    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();

        System.Environment.ExitCode = 0;
    }
    
    private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e)
    {
        e.Cancel = true;
        ApplicationLifetime.StopApplication();
    }
}

WaitForShutdownAsync

public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default)
{
    IHostApplicationLifetime 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);

    // 正是由于此处,程序 Run 起来后,在 applicationLifetime.ApplicationStopping 被触发前,能够一直保持运行状态
    await waitForStop.Task.ConfigureAwait(false);

    await host.StopAsync(CancellationToken.None).ConfigureAwait(false);
}

Host的整个启动流程,就差不多说完了。

服务接口

接下来咱们就从上面注册的默认服务中,挑几个详细聊一下。

IHostedService

public interface IHostedService
{
    Task StartAsync(CancellationToken cancellationToken);

    Task StopAsync(CancellationToken cancellationToken);
}

IHostedService用于在应用启动和关闭时,执行一些分外的操作。可以添加多个,都市被执行。

代码实例请查看接下来的IHostApplicationLifetime

IHostApplicationLifetime

通过该服务,可以针对程序启动后、正常关闭前和正常关闭后指定要执行的操作。

该服务生命周期被注册为Singleton,以是可以将该服务注册到任何类中。

该服务所拥有的3个属性ApplicationStartedApplicationStoppingApplicationStopped类型均为CancellationToken,当程序运行到某个生命周期节点时,就会触发对应属性的Cancel下令,进而执行注册的委托。

该服务的默认注册实现是Microsoft.Extensions.Hosting.Internal.ApplicationLifetime,代码很简朴,就是在程序启动后、正常关闭前和正常关闭后触发对应的3个属性。

另外,该服务还拥有StopApplication方式,用于请求住手当前应用程序的运行。

需要注重的是,IHostApplicationLifetime不允许注册自己的实现,只能使用微软提供的默认实现。

接下来就举个例子吧(配合IHostedService):

/// <summary>
/// 通用主机服务的生命周期事宜
/// </summary>
public class LifetimeEventsHostedService : IHostedService
{
    private readonly ILogger _logger;
    private readonly IHostApplicationLifetime _appLifetime;

    public LifetimeEventsHostedService(
        ILogger<LifetimeEventsHostedService> logger,
        IHostApplicationLifetime appLifetime)
    {
        _logger = logger;
        _appLifetime = appLifetime;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _appLifetime.ApplicationStarted.Register(OnStarted);
        _appLifetime.ApplicationStopping.Register(OnStopping);
        _appLifetime.ApplicationStopped.Register(OnStopped);

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }

    private void OnStarted()
    {
        _logger.LogInformation("App Started");
    }

    private void OnStopping()
    {
        _logger.LogInformation("App Stopping");
    }

    private void OnStopped()
    {
        _logger.LogInformation("App Stopped");
    }
}

// 注入服务
public void ConfigureServices(IServiceCollection services)
{
    services.AddHostedService<LifetimeEventsHostedService>();
}

IHostLifetime

该服务生命周期被注册为Singleton,以最后一个注册的实现为准。

默认注册的实现是Microsoft.Extensions.Hosting.Internal.ConsoleLifetime,该实现:

  • 监听Ctrl + C指令,并挪用IHostApplicationLifetime.StopApplication方式来关闭程序。
  • 排除RunAsyncWaitForShutdownAsync等扩展方式的壅闭挪用。

IHostEnvironment & IWebHostEnvironment

这两个服务生命周期均被注册为Singleton

通过IHostEnvironment,我们可以获取到:

  • ApplicationName
  • EnvironmentName
  • ContentRootPath
  • ContentRootFileProvider

IWebHostEnvironment继续于IHostEnvironment,在其基础上,又增添了:

  • WebRootPath
  • WebRootFileProvider

在 [01] Startup 中,我留下了一个问题,就是Startup类的组织函数中,IHostEnvironmentIWebHostEnvironment是统一个实例,这是为什么呢?接下来就来解开人人的疑惑:

或许你还会疑惑,显著我们使用的 Service Provider 要在 Startup.ConfigureServices 执行完毕后,才会被确立,为啥 Startup 的组织函数中却还能举行依赖注入呢?下面也会解答你得疑惑!

上面解读UseStartup时,看到一半就停下了,那是由于我要在这里和人人一起来更深入的明白:

  • 源码请戳UseStartup
internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider
{
    private void UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, HostBuilderContext context, IServiceCollection services, object instance = null)
    {
        var webHostBuilderContext = GetWebHostBuilderContext(context);
        var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
    
        ExceptionDispatchInfo startupError = null;
        ConfigureBuilder configureBuilder = null;
    
        try
        {
            // 确立 Startup 实例
            // 注重,这里使用的 Service Provider 是 HostServiceProvider (不是我们经常使用的谁人 service provider,此时它还没被确立),解决问题的焦点就在这个类内里
            instance ??= ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType);
            context.Properties[_startupKey] = instance;
    
            // Startup.ConfigureServices
            var configureServicesBuilder = StartupLoader.FindConfigureServicesDelegate(startupType, context.HostingEnvironment.EnvironmentName);
            var configureServices = configureServicesBuilder.Build(instance);
    
            // 挪用 Startup.ConfigureServices
            configureServices(services);
    
            // 将 Startup.ConfigureContainer 添加到 IHostBuilder.ConfigureContainer 中
            // 这个方式熟悉吗?你在使用 Autofac 的时刻是不是会有一个这个方式?
            var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName);
            if (configureContainerBuilder.MethodInfo != null)
            {
                var containerType = configureContainerBuilder.GetContainerType();
                _builder.Properties[typeof(ConfigureContainerBuilder)] = configureContainerBuilder;
    
                var actionType = typeof(Action<,>).MakeGenericType(typeof(HostBuilderContext), containerType);
    
                var configureCallback = typeof(GenericWebHostBuilder).GetMethod(nameof(ConfigureContainerImpl), BindingFlags.NonPublic | BindingFlags.Instance)
                                                 .MakeGenericMethod(containerType)
                                                 .CreateDelegate(actionType, this);
    
                // _builder.ConfigureContainer<T>(ConfigureContainer);
                typeof(IHostBuilder).GetMethod(nameof(IHostBuilder.ConfigureContainer))
                    .MakeGenericMethod(containerType)
                    .InvokeWithoutWrappingExceptions(_builder, new object[] { configureCallback });
            }
    
            // 注重,当执行完 ConfigureServices 和 ConfigureContainer 方式后,
            // 会将 Configure 方式剖析出来
            configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName);
        }
        catch (Exception ex) when (webHostOptions.CaptureStartupErrors)
        {
            startupError = ExceptionDispatchInfo.Capture(ex);
        }
    
        // Startup.Configure
        services.Configure<GenericWebHostServiceOptions>(options =>
        {
            options.ConfigureApplication = app =>
            {
                // Throw if there was any errors initializing startup
                startupError?.Throw();
    
                // 执行 Startup.Configure
                // 这下,你明了为什么之前可以通过 Options.ConfigureApplication 获取到 Startup.Configure 了吧?
                if (instance != null && configureBuilder != null)
                {
                    configureBuilder.Build(instance)(app);
                }
            };
        });
    }
    
    private class HostServiceProvider : IServiceProvider
    {
        private readonly WebHostBuilderContext _context;

        public HostServiceProvider(WebHostBuilderContext context)
        {
            _context = context;
        }

        // 该 ServieceProvider 中,仅提供了 IConfiguration、IHostEnvironment、IWebHostEnvironment 三种服务
        // 以是,在Startup的组织函数中,只能注入这三种服务
        public object GetService(Type serviceType)
        {
            // 很显然,IWebHostEnvironment 和 IHostEnvironment 返回的都是统一实例
            if (serviceType == typeof(Microsoft.Extensions.Hosting.IHostingEnvironment)
                || serviceType == typeof(Microsoft.AspNetCore.Hosting.IHostingEnvironment)
                || serviceType == typeof(IWebHostEnvironment)
                || serviceType == typeof(IHostEnvironment)
                )
            {
                return _context.HostingEnvironment;
            }

            if (serviceType == typeof(IConfiguration))
            {
                return _context.Configuration;
            }

            return null;
        }
    }
}

另有一个要点是:Startup组织方式中注入的IHostEnvironment和在Startup.Configure等方式中通过通例 Service Provider 剖析出来的IHostEnvironment实例是差其余。 缘故原由就是Startup组织方式中的依赖注入 Service Provider 和后面我们用的不是统一个,它们剖析的服务实例也不是统一个。

设置

ConfigureHostConfiguration—主机设置

我们可以在HostBuilder.ConfigureHostConfiguration方式中添加主机设置,多次挪用该方式也没关系,最终会将这些设置聚合起来

.ConfigureHostConfiguration(config =>
{
    config.SetBasePath(Directory.GetCurrentDirectory());
    config.AddEnvironmentVariables("MYAPPENVPREFIX_");
})

我们可以通过IHostEnvironment服务实现的属性来获取部门主机设置。

还可以在HostBuilder.ConfigureAppConfiguration方式中挪用HostBuilderContext.Configuration来获取主机设置。在执行完ConfigureAppConfiguration中的委托之后,在其他委托中通过HostBuilderContext.Configuration获取的就不再针对主机的设置了,而是针对应用的设置。

ConfigureAppConfiguration—应用设置

通过HostBuilder.ConfigureAppConfiguration方式,可以添加应用设置。同样的,该方式也可以多次举行挪用,最终会对设置举行聚合。

.ConfigureAppConfiguration((hostingContext, config) =>
{
    // 获取主机设置
    var hostingConfig = hostingContext.Configuration;

    var env = hostingContext.HostingEnvironment;

    config.AddJsonFile("mysettings.json", optional: true, reloadOnChange: true)
          .AddJsonFile($"mysettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
})

结语

  • 一些常用的设置项注释可以接见官方文档

  • 由于默认只能在Development环境时才会启用局限验证(scope validation)和依赖关系验证(dependency validation),以是,若是想要手动举行设置,可以通过UseDefaultServiceProvider(实在默认逻辑的源码内里也是使用的该扩展方式)

.UseDefaultServiceProvider((context, options) =>
{
    options.ValidateScopes = true;
    options.ValidateOnBuild = true;
});

信托你读完本篇文章,一定对ASP.NET Core主机的启动流程,有了新的熟悉!

皇冠正网www.huangguan.us)是一个开放皇冠正网即时比分、皇冠正网注册的平台。皇冠正网平台(www.huangguan.us)提供最新皇冠登录,皇冠APP下载包含新皇冠体育代理、会员APP。

发布评论