CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
理解ASP.NET Core - 主机(Host)
0
.NetCore
小笨蛋
发布于:2022年09月12日
更新于:2022年12月27日
148
#custom-toc-container
本文会涉及部分 Host 相关的源码,并会附上 github 源码地址,不过为了降低篇幅,我会删除一些不涉及的代码。 为了方便,还是建议你将源码(.net5)[runtime](https://github.com/dotnet/runtime/tree/release/5.0 "runtime") 和 [aspnetcore](https://github.com/dotnet/aspnetcore/tree/release/5.0 "aspnetcore") 下载下来,通过VS等工具阅读 请耐心阅读! ### Generic Host & WebHost 在.NET Core 2.x时,ASP.NET Core 默认使用的是WebHost: ```csharp public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup
(); } ``` 而到了.NET Core 3.x,ASP.NET Core 默认选择使用Generic Host: ```csharp 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
(); }); } ``` **那么,为什么.NET团队要将Web主机(Web Host)替换为通用主机(Generic Host)呢?** > 参考 [What is the difference between Host and WebHost class in asp.net core](https://stackoverflow.com/questions/59745401/what-is-the-difference-between-host-and-webhost-class-in-asp-net-core "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在.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的启动流程图: 因此,接下来咱们就聊一下通用主机。 [![](/uploads/images/20221226/223131-5a92dad4f1cf4e219082dac18d9299e7.jpg)](https://www.codesnippet.cn) [![](/uploads/images/20221226/223322-2415131e1d7e4dc5958b8ba5282c095b.jpg)](https://www.codesnippet.cn) 请大家就着上面这张两图食用以下内容。 #### ConfigureXXX 在深入之前,大家要先了解一下ConfigureHostConfiguration、ConfigureAppConfiguration、ConfigureServices等方法到底做了什么。其实,很简单,就是将委托暂存到了一个临时变量里。 ```csharp public class HostBuilder : IHostBuilder { private List
> _configureHostConfigActions = new List
>(); private List
> _configureAppConfigActions = new List
>(); private List
> _configureServicesActions = new List
>(); public IHostBuilder ConfigureHostConfiguration(Action
configureDelegate) { _configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } public IHostBuilder ConfigureAppConfiguration(Action
configureDelegate) { _configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } public IHostBuilder ConfigureServices(Action
configureDelegate) { _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } } ``` #### Host.CreateDefaultBuilder(args) - 源码请戳[CreateDefaultBuilder](https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/Microsoft.Extensions.Hosting/src/Host.cs "CreateDefaultBuilder") ```csharp 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
(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](https://github.com/dotnet/aspnetcore/blob/release/5.0/src/DefaultBuilder/src/GenericHostBuilderExtensions.cs "ConfigureWebHostDefaults")、[ConfigureWebHost](https://github.com/dotnet/aspnetcore/blob/release/5.0/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs "ConfigureWebHost")、[GenericWebHostBuilder](https://github.com/dotnet/aspnetcore/blob/release/5.0/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs "GenericWebHostBuilder")、[WebHost.ConfigureWebDefaults](https://github.com/dotnet/aspnetcore/blob/49f14b8e0d409c1ad9c38585c77676a85f2336b3/src/DefaultBuilder/src/WebHost.cs#L215 "WebHost.ConfigureWebDefaults")、[UseStartup](https://github.com/dotnet/aspnetcore/blob/49f14b8e0d409c1ad9c38585c77676a85f2336b3/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs#L162 "UseStartup")、[GenericWebHostBuilder.UseStartup](https://github.com/dotnet/aspnetcore/blob/49f14b8e0d409c1ad9c38585c77676a85f2336b3/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs#L206 "GenericWebHostBuilder.UseStartup")、[GenericWebHostService](https://github.com/dotnet/aspnetcore/blob/release/5.0/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs "GenericWebHostService") ```csharp public static class GenericHostBuilderExtensions { public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action
configure) { return builder.ConfigureWebHost(webHostBuilder => { WebHost.ConfigureWebDefaults(webHostBuilder); // 执行 UseStartup 等 configure(webHostBuilder); }); } } public static class GenericHostWebHostBuilderExtensions { public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action
configure) { return builder.ConfigureWebHost(configure, _ => { }); } public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action
configure, Action
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
()); return builder; } } ``` 上面这段代码重点有两个: - 一个是GenericWebHostBuilder这个类,记住它,ConfigureWebHostDefaults委托中的webBuilder参数就是它! - 另一个是GenericWebHostService。 下面,我们先看一下GenericWebHostBuilder的构造函数: ```csharp 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
(); services.Configure
(options => { options.WebHostOptions = webHostOptions; options.HostingStartupExceptions = _hostingStartupErrors; }); var listener = new DiagnosticListener("Microsoft.AspNetCore"); services.TryAddSingleton
(listener); services.TryAddSingleton
(listener); services.TryAddSingleton
(); services.TryAddScoped
(); services.TryAddSingleton
(); // 注册 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
(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
(); // 注意这里对 _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
()) { 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: ```csharp 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
(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
>( new ConfigurationChangeTokenSource
(hostingContext.Configuration)); services.AddTransient
(); // 当环境变量 ASPNETCORE_FORWARDEDHEADERS_ENABLED 为 true 时,添加转接头中间件(Forwarded Headers) if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase)) { services.Configure
(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); services.AddTransient
(); } services.AddRouting(); }) // 启用IIS集成 .UseIIS() .UseIISIntegration(); } } ``` 我们通常会在ConfigureWebHostDefaults扩展方法的委托中调用UseStartup来指定Startup类,下面我们就来看一下UseStartup到底做了什么:将Startup.ConfigureServices中要注册的服务添加到ConfigureServices的委托中 ```csharp 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.Configure、Startup.Configure等) ```csharp internal class GenericWebHostService : IHostedService { // 构造函数注入 public GenericWebHostServiceOptions Options { get; } // 构造函数注入 public IEnumerable
StartupFilters { get; } public async Task StartAsync(CancellationToken cancellationToken) { // ...删除了一些代码 RequestDelegate application = null; try { // 这里取到了 Startup.Configure // 可能你不知道为什么这里可以取到,别着急,文章后面会为你解释的 Action
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](https://github.com/dotnet/runtime/blob/ba8d866e472e3b45734138d861845932c25de929/src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.cs#L119 "Build") ```csharp public class HostBuilder : IHostBuilder { public IHost Build() { // 加载主机(Host)配置 BuildHostConfiguration(); // 实例化 HostingEnvironment CreateHostingEnvironment(); // 实例化 HostBuilderContext CreateHostBuilderContext(); // 加载应用(App)配置 BuildAppConfiguration(); // 注册服务并创建 Service Provider CreateServiceProvider(); // 生成 IHost 实例并返回 return _appServices.GetRequiredService
(); } } ``` BuildHostConfiguration ```csharp public class HostBuilder : IHostBuilder { private void BuildHostConfiguration() { IConfigurationBuilder configBuilder = new ConfigurationBuilder() .AddInMemoryCollection(); // 加载主机(Host)配置(同时会执行上面所说的 IHostingStartup.Configure) foreach (Action
buildAction in _configureHostConfigActions) { buildAction(configBuilder); } _hostConfiguration = configBuilder.Build(); } } ``` CreateHostingEnvironment ```csharp 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 ```csharp public class HostBuilder : IHostBuilder { private void CreateHostBuilderContext() { _hostBuilderContext = new HostBuilderContext(Properties) { HostingEnvironment = _hostingEnvironment, Configuration = _hostConfiguration }; } } ``` BuildAppConfiguration ```csharp public class HostBuilder : IHostBuilder { private void BuildAppConfiguration() { IConfigurationBuilder configBuilder = new ConfigurationBuilder() .SetBasePath(_hostingEnvironment.ContentRootPath) .AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true); foreach (Action
buildAction in _configureAppConfigActions) { buildAction(_hostBuilderContext, configBuilder); } _appConfiguration = configBuilder.Build(); _hostBuilderContext.Configuration = _appConfiguration; } } ``` CreateServiceProvider ```csharp public class HostBuilder : IHostBuilder { private void CreateServiceProvider() { var services = new ServiceCollection(); services.AddSingleton
(_hostingEnvironment); // 注册 IHostEnvironment services.AddSingleton
(_hostingEnvironment); // 注册 HostBuilderContext services.AddSingleton(_hostBuilderContext); // 注册 IConfiguration,所以能在 Startup 中进行构造函数注入 services.AddSingleton(_ => _appConfiguration); services.AddSingleton
(s => (IApplicationLifetime)s.GetService
()); services.AddSingleton
(); // 注意这里注册了 IHostLifetime 服务的实例 ConsoleLifetime services.AddSingleton
(); // 注册 IHost 实例 services.AddSingleton
(); services.AddOptions(); services.AddLogging(); // 执行 ConfigureServices 方法中的委托进行服务注册 // 包括使用扩展方法 ConfigureServices、 Startup.ConfigureServices 等设置的委托 foreach (Action
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
(); } } ``` #### Run - 源码请戳[Run](https://github.com/dotnet/runtime/blob/ba8d866e472e3b45734138d861845932c25de929/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs#L48 "Run")、[StartAsync](https://github.com/dotnet/runtime/blob/ba8d866e472e3b45734138d861845932c25de929/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs#L39 "StartAsync") ```csharp 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 ```csharp 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
>(); 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 ```csharp public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default) { IHostApplicationLifetime applicationLifetime = host.Services.GetService
(); token.Register(state => { ((IHostApplicationLifetime)state).StopApplication(); }, applicationLifetime); var waitForStop = new TaskCompletionSource
(TaskCreationOptions.RunContinuationsAsynchronously); applicationLifetime.ApplicationStopping.Register(obj => { var tcs = (TaskCompletionSource
)obj; tcs.TrySetResult(null); }, waitForStop); // 正是由于此处,程序 Run 起来后,在 applicationLifetime.ApplicationStopping 被触发前,能够一直保持运行状态 await waitForStop.Task.ConfigureAwait(false); await host.StopAsync(CancellationToken.None).ConfigureAwait(false); } ``` Host的整个启动流程,就差不多说完了。 ### 服务接口 接下来咱们就从上面注册的默认服务中,挑几个详细聊一下。 #### IHostedService ```csharp public interface IHostedService { Task StartAsync(CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken); } ``` HostedService用于在应用启动和关闭时,执行一些额外的操作。可以添加多个,都会被执行。 代码实例请查看接下来的IHostApplicationLifetime。 #### IHostApplicationLifetime 通过该服务,可以针对程序启动后、正常关闭前和正常关闭后指定要执行的操作。 该服务生命周期被注册为Singleton,所以可以将该服务注册到任何类中。 该服务所拥有的3个属性ApplicationStarted、ApplicationStopping和ApplicationStopped类型均为CancellationToken,当程序运行到某个生命周期节点时,就会触发对应属性的Cancel命令,进而执行注册的委托。 该服务的默认注册实现是Microsoft.Extensions.Hosting.Internal.ApplicationLifetime,代码很简单,就是在程序启动后、正常关闭前和正常关闭后触发对应的3个属性。 另外,该服务还拥有StopApplication方法,用于请求停止当前应用程序的运行。 **需要注意的是,IHostApplicationLifetime不允许注册自己的实现,只能使用微软提供的默认实现。** 接下来就举个例子吧(配合IHostedService): ```csharp ///
/// 通用主机服务的生命周期事件 ///
public class LifetimeEventsHostedService : IHostedService { private readonly ILogger _logger; private readonly IHostApplicationLifetime _appLifetime; public LifetimeEventsHostedService( ILogger
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
(); } ``` #### IHostLifetime 该服务生命周期被注册为Singleton,以最后一个注册的实现为准。 默认注册的实现是Microsoft.Extensions.Hosting.Internal.ConsoleLifetime,该实现: - 监听Ctrl + C指令,并调用IHostApplicationLifetime.StopApplication方法来关闭程序。 - 解除RunAsync和WaitForShutdownAsync等扩展方法的阻塞调用。 #### IHostEnvironment & IWebHostEnvironment 这两个服务生命周期均被注册为Singleton。 通过IHostEnvironment,我们可以获取到: - ApplicationName - EnvironmentName - ContentRootPath - ContentRootFileProvider IWebHostEnvironment继承于IHostEnvironment,在其基础上,又增加了: - WebRootPath - WebRootFileProvider 在 [[01] Startup](https://www.codesnippet.cn/home/list/181 "[01] Startup") 中,我留下了一个问题,就是Startup类的构造函数中,IHostEnvironment和IWebHostEnvironment是同一个实例,这是为什么呢?接下来就来解开大家的疑惑: > 或许你还会疑惑,明明我们使用的 Service Provider 要在 Startup.ConfigureServices 执行完毕后,才会被创建,为啥 Startup 的构造函数中却还能进行依赖注入呢?下面也会解答你得疑惑! 上面解读UseStartup时,看到一半就停下了,那是因为我要在这里和大家一起来更深入的理解: - 源码请戳[UseStartup](https://github.com/dotnet/aspnetcore/blob/b6a4909420d517a060b2d10a4522620c58f42f95/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs#L243 "UseStartup") ```csharp 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
(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
(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方法中添加主机配置,多次调用该方法也没关系,最终会将这些配置聚合起来 ```csharp .ConfigureHostConfiguration(config => { config.SetBasePath(Directory.GetCurrentDirectory()); config.AddEnvironmentVariables("MYAPPENVPREFIX_"); }) ``` 我们可以通过IHostEnvironment服务实现的属性来获取部分主机配置。 还可以在HostBuilder.ConfigureAppConfiguration方法中调用HostBuilderContext.Configuration来获取主机配置。在执行完ConfigureAppConfiguration中的委托之后,在其他委托中通过HostBuilderContext.Configuration获取的就不再针对主机的配置了,而是针对应用的配置。 #### ConfigureAppConfiguration—应用配置 通过HostBuilder.ConfigureAppConfiguration方法,可以添加应用配置。同样的,该方法也可以多次进行调用,最终会对配置进行聚合。 ```csharp .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); }) ``` ### 结语 - 一些常用的配置项解释可以访问[官方文档](https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-5.0#settings-for-all-app-types "官方文档") - 由于默认只能在Development环境时才会启用范围验证(scope validation)和依赖关系验证(dependency validation),所以,如果想要手动进行配置,可以通过UseDefaultServiceProvider(其实默认逻辑的源码里面也是使用的该扩展方法) ```csharp .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = true; options.ValidateOnBuild = true; }); ``` 相信你读完本篇文章,一定对ASP.NET Core主机的启动流程,有了新的认识!
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024