一、托管服务(IHostedService)
在业务场景中经常需要后台服务不停的或定时处理一些任务,在 asp.net中会使用windows服务来处理,在 asp.net core中可以使用托管服务来实现,托管服务是一个类,具有实现IHostService接口的后台任务逻辑。托管服务必须实现IHostedService接口。抽象类BackgroundService是IHostedService的派生类,实现了IHostService接口定义的方法,因此自定义的托管服务类也可以继承BackgroundService实现ExecuteAsync()方法即可。
1、IHostService
直接上IHostService的定义。托管服务必须实现IHostedService接口:
public interface IHostedService { Task StartAsync(CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken); }
-
StartAsync:当应用程序主机准备好启动服务时触发。
-
StartAsync:当应用程序主机执行正常关闭时触发。
2、使用IHostedService自定义托管服务
自定义托管服务类直接继承IHostedService和IDisposable接口。定时后台任务使用 System.Threading.Timer 类。计时器触发任务的 DoWork
方法。 在 StopAsync
上禁用计时器,并在 Dispose
上处置服务容器时处置计时器:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0; private Timer _timer; public Task StartAsync(CancellationToken stoppingToken) { Console.WriteLine("Timed Hosted Service running."); _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5)); return Task.CompletedTask; } private void DoWork(object state) { var count = Interlocked.Increment(ref executionCount); Console.WriteLine("Timed Hosted Service is working. Count: {Count}", count); } public Task StopAsync(CancellationToken stoppingToken) { Console.WriteLine("Timed Hosted Service is stopping."); _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } public void Dispose() { _timer?.Dispose(); } }
使用 AddHostedService
扩展方法在 IHostBuilder.ConfigureServices
(Program.cs) 中注册该服务:
static void Main(string[] args)
{
try
{
var host = new HostBuilder().ConfigureServices((hostContext, services) =>
{
services.AddHostedService<TimedHostedService>();
}).Build();
host.Run();
Console.WriteLine("Hello World!");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
3、使用BackgroundService 实现自定义托管服务
由于抽象类BackgroundService已经实现了IHostService接口定义的方法,只需要写子类去继承BackgroundService, 在自己的自定义托管服务类中实现ExecuteAsync()方法
public class MyBackGroundService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { Console.WriteLine("MyBackGroundService doing"); //延迟500毫秒执行 相当于使用了定时器 await Task.Delay(500, stoppingToken); } } }
使用 AddHostedService
扩展方法在 IHostBuilder.ConfigureServices
(Program.cs) 中注册该服务:
static void Main(string[] args)
{
try { var host = new HostBuilder().ConfigureServices((hostContext, services) => { services.AddHostedService<MyBackGroundService>(); }).Build(); host.Run(); Console.WriteLine("Hello World!"); Console.ReadLine(); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
托管的服务总是会被定义成IHostedService接口的实现类型。
public interface IHostedService { Task StartAsync(CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken); }
该接口仅定义了两个用来启动和关闭自身服务的方法。当作为宿主的IHost对象被启动的时候,它会利用依赖注入框架激活每个注册的IHostedService服务,并通过调用StartAsync方法来启动它们。当服务关闭的时候,作为服务宿主的IHost对象会被关闭,由它承载的每个IHostedService服务对象的StopAsync方法也随之被调用。
4、通用主机和托管服务的关系
(1)通用主机是什么?
就是IHostBuilder/IHost为核心的体系。下面会详细介绍。
(2)为什么需要通用主机?
上边已经介绍过,有些后台服务需要不停的或定时处理一些任务,在 asp.net中会使用windows服务来处理,在 asp.net core中可以使用托管服务来实现,Windows服务也好,托管服务也好,都不过是一个类,那么如何将这些服务类托管起来,运行起来呢?ASP.NET Core提供了以IHostBuilder/IHost为核心的体系用来寄宿托管服务,IHostBuilder/IHost位于.NET Runtime 而不是ASP.NET Core 中,笼统来说,IHostBuilder/IHost能够与操作系统交互,以IHostedService接口实现的托管服务寄宿到IHostBuilder/IHost就可以在操作系统上运行起来了。利用通用主机可以将任意一个或者多个长时间运行的服务寄宿于托管进程中。任何需要在后台长时间运行的操作都可以定义成标准化的服务并利用通用主机来寄宿。
那么,一个ASP.NET Core应用本质上是一个需要长时间运行的服务,开启这个服务是为了启动一个网络监听器,当监听到抵达的HTTP请求之后,该监听器会将请求传递给应用提供的管道进行处理,管道完成了对请求处理之后会生成HTTP响应,并通过监听器返回客户端。从asp.net core源码中看到GenericWebHostService就是一个托管服务,用来实现一系列的工作,为了让GenericWebHostService运行起来,ASP.NET Core提供了以IHostBuilder/IHost为核心的体系。
反过来理解也可以,Asp.net Core的Runtime中构建了以IHostBuilder/IHost为核心的体系,目的是能够跨平台,能够与不同的操作系统打交道,是Asp.net Core应用的基础。一个Asp.Net Core应用的一些核心工作交给托管服务处理,托管服务依赖IHostBuilder/IHost为核心的体系。关系图如下:
二、通用主机模型
ASP.NET Core应用程序拥有一个内置的Self-Hosted(自托管)的Web Server(Web服务器),用来处理外部请求。不管是托管还是自托管,都离不开Host(宿主),即通用主机。在ASP.NET Core应用中通过配置并启动一个Host来完成应用程序的启动和其生命周期的管理。而Host的主要的职责就是Web Server的配置和Pilpeline(请求处理管道)的构建。
通用主机也叫泛型主机,为什么通用?是因为能够与不同的操作系统交互。为什么能够和不同的操作系统交互?笼统来说,通用主机能够适配不同类型的Http服务器,其中kestrel服务器就是跨平台的关键。具体可以参考NetCore解读服务器。
通用主机主要由三个核心对象组成:多个通过IHostedService接口表示的服务被寄宿于通过IHost接口表示的宿主上,IHostBuilder接口表示IHost对象的构建者。
//定义托管服务GenericWebHostService
internal class GenericWebHostService : IHostedService{}
//GenericWebHostService 托管服务承载于Ihost宿主。
_hostBuilder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>()).Build().Run();
在ASP.NET Core应用中通过配置并启动一个Host来完成应用程序的启动和其生命周期的管理,大概流程如下图:
类Host调用CreateDefaultBuilder方法创建HostBuilder实例,HostBuilder实例调用相应的方法和扩展方法完成基础配置后,调用Build()方法创建IHost实例,IHost实例再次调用Run()方法完成通用主机的启动。下面将会详细介绍通用主机模型的实现。
三、IHost
先来介绍下IHost,IHost位于.NET Runtime 而不是ASP.NET Core 中。通过IHostedService接口表示的托管服务GenericWebHostService 最终被承载于通过IHost接口表示的宿主上。一般来说,aspnetcore应用整个生命周期内只会创建一个IHost对象,启动和关闭应用程序本质上就是启动和关闭作为宿主的IHost对象。
public interface IHost : IDisposable { IServiceProvider Services { get; } Task StartAsync(CancellationToken cancellationToken = default); Task StopAsync(CancellationToken cancellationToken = default); }
IHost接口派生于IDisposable接口,所以当它在关闭之后,应用程序还会调用其Dispose方法作一些额外的资源释放工作。IHost接口的Services属性返回作为依赖注入容器的IServiceProvider对象,该对象提供了服务承载过程中所需的服务实例,其中就包括需要承载的IHostedService服务。定义在IHost接口中的StartAsync和StopAsync方法完成了针对服务宿主的启动和关闭。
ASP.NET Core 所使用的 Host 是一个通用的宿主,它位于.NET Runtime 而不是ASP.NET Core 中。总而言之,Host是封装应用程序资源的对象,为它所承载的应用提供环境和基本的功能,封装的内容主要包括:
- DI容器
- 日志
- 配置
- IHostedService实现,启动HTTP服务器的实现的服务就是一个IHostedService(GenericWebHostService)
1、Run方法
/// <summary> /// Runs an application and returns a Task that only completes when the token is triggered or shutdown is triggered. /// </summary> /// <param name="host">The <see cref="IHost"/> to run.</param> /// <param name="token">The token to trigger shutdown.</param> /// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns> 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(); } } } /// <summary> /// Returns a Task that completes when shutdown is triggered via the given token. /// </summary> /// <param name="host">The running <see cref="IHost"/>.</param> /// <param name="token">The token to trigger shutdown.</param> /// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns> 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(); }
IHost是由 IHostBuilder.Build() 方法创建的,所以我们通过对 IHostBuilder 的配置来实现对 Host 的配置。下面会介绍到IHostBuilder 。
四、类Host
此Host非IHost中的Host,此处的Host是一个类,提供了创建HostBuilder实例的方法,具体看下源码:
namespace Microsoft.Extensions.Hosting
{
/// <summary>
/// Provides convenience methods for creating instances of <see cref="IHostBuilder"/> with pre-configured defaults.
/// </summary>
public static class Host { /// <summary> /// Initializes a new instance of the <see cref="HostBuilder"/> class with pre-configured defaults. /// </summary> /// <remarks> /// The following defaults are applied to the returned <see cref="HostBuilder"/>: /// <list type="bullet"> /// <item><description>set the <see cref="IHostEnvironment.ContentRootPath"/> to the result of <see cref="Directory.GetCurrentDirectory()"/></description></item> /// <item><description>load host <see cref="IConfiguration"/> from "DOTNET_" prefixed environment variables</description></item> /// <item><description>load app <see cref="IConfiguration"/> from 'appsettings.json' and 'appsettings.[<see cref="IHostEnvironment.EnvironmentName"/>].json'</description></item> /// <item><description>load app <see cref="IConfiguration"/> from User Secrets when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development' using the entry assembly</description></item> /// <item><description>load app <see cref="IConfiguration"/> from environment variables</description></item> /// <item><description>configure the <see cref="ILoggerFactory"/> to log to the console, debug, and event source output</description></item> /// <item><description>enables scope validation on the dependency injection container when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development'</description></item> /// </list> /// </remarks> /// <returns>The initialized <see cref="IHostBuilder"/>.</returns> public static IHostBuilder CreateDefaultBuilder() => CreateDefaultBuilder(args: null); /// <summary> /// Initializes a new instance of the <see cref="HostBuilder"/> class with pre-configured defaults. /// </summary> /// <remarks> /// The following defaults are applied to the returned <see cref="HostBuilder"/>: /// <list type="bullet"> /// <item><description>set the <see cref="IHostEnvironment.ContentRootPath"/> to the result of <see cref="Directory.GetCurrentDirectory()"/></description></item> /// <item><description>load host <see cref="IConfiguration"/> from "DOTNET_" prefixed environment variables</description></item> /// <item><description>load host <see cref="IConfiguration"/> from supplied command line args</description></item> /// <item><description>load app <see cref="IConfiguration"/> from 'appsettings.json' and 'appsettings.[<see cref="IHostEnvironment.EnvironmentName"/>].json'</description></item> /// <item><description>load app <see cref="IConfiguration"/> from User Secrets when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development' using the entry assembly</description></item> /// <item><description>load app <see cref="IConfiguration"/> from environment variables</description></item> /// <item><description>load app <see cref="IConfiguration"/> from supplied command line args</description></item> /// <item><description>configure the <see cref="ILoggerFactory"/> to log to the console, debug, and event source output</description></item> /// <item><description>enables scope validation on the dependency injection container when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development'</description></item> /// </list> /// </remarks> /// <param name="args">The command line args.</param> /// <returns>The initialized <see cref="IHostBuilder"/>.</returns> 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; } } }
类Host提供了两个重载方法,用于生成HostBuilder实例。下面将详细介绍CreateDefaultBuilder方法。
五、IHostBuilder
通用主机的配置基本上都是在IHostBuilder中实现的,IHostBuilder位于.NET Runtime 而不是ASP.NET Core 中。顾名思义,IHostBuilder是用来构建IHost宿主的,IHost对象在应用启动过程中采用Builder模式由对应的IHostBuilder对象来创建,HostBuilder类型是对IHostBuilder接口的默认实现。通过下面方法创建一个HostBuilder对象并进行相应的配置:
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseKestrel(); webBuilder.UseStartup<Startup>(); });
1、Host.CreateDefaultBuilder(string[] args)
public static IHostBuilder CreateDefaultBuilder(string[] args);//返回IHostBuilder
Host.CreateDefaultBuilder()用于创建HostBuilder实例,并使用预配置默认值初始化 HostBuilder 类的新实例。以下默认值将应用于返回的 HostBuilder :
-
将设置 内容根目录ContentRootPath 的结果为GetCurrentDirectory()
-
从提供的命令行参数加载主机
-
载入 appsettings.json文件和appsettings.[enviromentName].json文件的配置
-
开发模式下,保存项目程序集到用户机密
-
从环境变量中加载应用
-
从提供的命令行参数加载应用
-
配置 ILoggerFactory 以记录到控制台、调试和事件源输出
-
开发模式下,在DI容器上启用作用域验证 EnvironmentName
具体实现看下Host.CreateDefaultBuilder()源码:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/Host.cs
/// <summary> /// Initializes a new instance of the <see cref="HostBuilder"/> class with pre-configured defaults. /// </summary> /// <remarks> /// The following defaults are applied to the returned <see cref="HostBuilder"/>: /// <list type="bullet"> /// <item><description>set the <see cref="IHostEnvironment.ContentRootPath"/> to the result of <see cref="Directory.GetCurrentDirectory()"/></description></item> /// <item><description>load host <see cref="IConfiguration"/> from "DOTNET_" prefixed environment variables</description></item> /// <item><description>load host <see cref="IConfiguration"/> from supplied command line args</description></item> /// <item><description>load app <see cref="IConfiguration"/> from 'appsettings.json' and 'appsettings.[<see cref="IHostEnvironment.EnvironmentName"/>].json'</description></item> /// <item><description>load app <see cref="IConfiguration"/> from User Secrets when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development' using the entry assembly</description></item> /// <item><description>load app <see cref="IConfiguration"/> from environment variables</description></item> /// <item><description>load app <see cref="IConfiguration"/> from supplied command line args</description></item> /// <item><description>configure the <see cref="ILoggerFactory"/> to log to the console, debug, and event source output</description></item> /// <item><description>enables scope validation on the dependency injection container when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development'</description></item> /// </list> /// </remarks> /// <param name="args">The command line args.</param> /// <returns>The initialized <see cref="IHostBuilder"/>.</returns> public static IHostBuilder CreateDefaultBuilder(string[] args) { var builder = new HostBuilder(); //将设置 内容根目录ContentRootPath 的结果为GetCurrentDirectory() 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; //载入 appsettings.json文件和appsettings.[enviromentName].json文件的配置 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); } }) //配置 ILoggerFactory 以记录到控制台、调试和事件源输出 .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(); } }) //开发模式下,在DI容器上启用作用域验证 EnvironmentName .UseDefaultServiceProvider((context, options) => { var isDevelopment = context.HostingEnvironment.IsDevelopment(); options.ValidateScopes = isDevelopment; options.ValidateOnBuild = isDevelopment; }); return builder; }
HostBuilder创建完成后,下面先来看下HostBuilder的一些扩展方法
2、IHostBuilder方法和扩展方法
Host.CreateDefaultBuilder(args)返回了HostBuilder实例,并做了一些默认初始化的配置。IHostBuilder也提供了一些方法和扩展方法用来进一步的配置,IHostBuilder源码如下:
namespace Microsoft.Extensions.Hosting
{
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); } }
类HostBuilder实现了IHostBuilder,具体实现可以参考源码:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/HostBuilder.cs
- ConfigureAppConfiguration:载入 appsettings.json文件和appsettings.[enviromentName].json文件的配置。可多次调用,累加结果。
- ConfigureContainer:配置DI容器,可多次调用,累加结果。
- ConfigureHostConfiguration:设置生成器自身的配置。 这将用于初始化 IHostEnvironment 以便稍后在生成过程中使用,设置内容根目录。 可多次进行调用,并累加结果。
- ConfigureServices:为Host的容器添加服务,可多次调用,并累加结果。
- UseServiceProviderFactory:设置用于创建服务提供者的工厂
IHostBuilder同时提供了一些扩展方法,以下是部分扩展方法,扩展方法存在于类HostingHostBuilderExtensions中具体实现源码可以参考:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/HostingHostBuilderExtensions.cs
- ConfigureLogging,该方法提供ILoggingBuilder对象对日志进行配置。
- ConfigureWebHost,该方法提供IWebHostBuilder对象对ASP.NET Core应用进行配置。后边会详细介绍
- UseConsoleLifetime,该方法使Host监听Console的停止事件如:Ctrl+C 或 SIGTERM。
- UseContentRoot,指定Host的内容根目录。
- UseDefaultServiceProvider,使用默认的IServiceProvider,并对其进行配置。
- UseEnvironment,指定Host的环境参数。
3、IHostBuilder.ConfigureWebHostDefaults()
ConfigureWebHostDefaults也是IHostBuilder的扩展方法,这里重点介绍下。上面介绍的配置主要是 Host 的通用配置,这些配置是各类应用所需的基础功能,所以到目前为止我们的 Host 仍然不具备承载 Web 应用的能力,所以IHostBuilder的扩展方法ConfigureWebHostDefaults引入IWebHostBuilder实例,为 Host 添加一个“WebHost”应具备的核心功能,下面是ConfigureWebHostDefaults的默认配置:
- 使用 Kestrel 作为Web服务器,并载入配置文件中 Kestrel 的相关配置。
- 配置默认的静态文件目录。
- 添加HostFiltering中间件。
- 如果ASPNETCORE_FORWARDEDHEADERS_ENABLED为true则添加ForwardedHeaders中间件
- 配置为默认使用IIS集成模式。
具体试下,请参考ConfigureWebHostDefaults源码:
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore; namespace Microsoft.Extensions.Hosting { /// <summary> /// Extension methods for configuring the <see cref="IHostBuilder" />. /// </summary> public static class GenericHostBuilderExtensions { /// <summary> /// Configures a <see cref="IHostBuilder" /> with defaults for hosting a web app. This should be called /// before application specific configuration to avoid it overwriting provided services, configuration sources, /// environments, content root, etc. /// </summary> /// <remarks> /// The following defaults are applied to the <see cref="IHostBuilder"/>: /// <list type="bullet"> /// <item><description>use Kestrel as the web server and configure it using the application's configuration providers</description></item> /// <item><description>configure <see cref="IWebHostEnvironment.WebRootFileProvider"/> to include static web assets from projects referenced by the entry assembly during development</description></item> /// <item><description>adds the HostFiltering middleware</description></item> /// <item><description>adds the ForwardedHeaders middleware if ASPNETCORE_FORWARDEDHEADERS_ENABLED=true,</description></item> /// <item><description>enable IIS integration</description></item> /// </list> /// </remarks> /// <param name="builder">The <see cref="IHostBuilder" /> instance to configure.</param> /// <param name="configure">The configure callback</param> /// <returns>A reference to the <paramref name="builder"/> after the operation has completed.</returns>
//IHostBuilder的扩展方法,用于接入IWebHostBuilder实例 public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure) { if (configure is null) { throw new ArgumentNullException(nameof(configure)); } //调用IhostBuilder扩展方法ConfigureWebHost,配置Host return builder.ConfigureWebHost(webHostBuilder => {
//调用方法ConfigureWebDefaults,配置webhost WebHost.ConfigureWebDefaults(webHostBuilder); configure(webHostBuilder); }); } } }
从上面源码中可以看出,ConfigureWebHostDefaults中调用了ConfigureWebHost和ConfigureWebDefaults两个方法。其中ConfigureWebHost是IHostBuilder的扩展方法,主要是对ASP.NET Core应用进行配置。ConfigureWebDefaults用来对WebHost做默认配置。先来看下ConfigureWebDefaults。
(1)ConfigureWebDefaults
ConfigureWebDefaults是WebHost的默认配置,ConfigureWebDefaults的源码定义如下:
//IwebHostBuilder的基本配置
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
{
//配置默认的静态文件目录。
builder.ConfigureAppConfiguration((ctx, cb) => { if (ctx.HostingEnvironment.IsDevelopment()) { StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration); } }); //使用 Kestrel 作为Web服务器,并载入配置文件中 Kestrel 的相关配置。 builder.UseKestrel((builderContext, options) => { options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true); }) .ConfigureServices((hostingContext, services) => { // Fallback services.PostConfigure<HostFilteringOptions>(options => { if (options.AllowedHosts == null || options.AllowedHosts.Count == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); // Fall back to "*" to disable. options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); //添加HostFiltering中间件。 services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>( new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); //如果ASPNETCORE_FORWARDEDHEADERS_ENABLED为true则添加ForwardedHeaders中间件 services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>(); services.AddTransient<IConfigureOptions<ForwardedHeadersOptions>, ForwardedHeadersOptionsSetup>(); services.AddRouting(); }) //配置为默认使用IIS集成模式。 .UseIIS() .UseIISIntegration(); }
从源码中可以看出,IHostBuilder.ConfigureWebHostDefaults()相关的配置都集中在ConfigureWebDefaults方法中:
- 使用 Kestrel 作为Web服务器,并载入配置文件中 Kestrel 的相关配置。
- 配置默认的静态文件目录。
- 添加HostFiltering中间件。
- 如果ASPNETCORE_FORWARDEDHEADERS_ENABLED为true则添加ForwardedHeaders中间件
- 配置为默认使用IIS集成模式。
(2)ConfigureWebHost
ConfigureWebHost东东有点多,不在这里陈述了,具体在下面五、ConfigureWebHost中陈述。
4、IWebHostBuilder
上边介绍了IHost/IHostBuilder体系的整个流程,其中IHostBuilder.ConfigureWebHostDefaults的参数类型为Action<IWebHostBuilder>,也介绍了IHostBuilder.ConfigureWebHostDefaults方法中对Action<IWebHostBuilder>类型的委托处理过程。那么传入的Action<IWebHostBuilder>都做了什么事情呢?换句话说,通过HostBuilder.ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)方法引入了IWebHostBuilder,默认为 Host 添加一个“WebHost”应具备的核心功能。默认配置完成后我们可以根据IWebHostBuilder的扩展方法来进行自定义的配置了。
- Configure,指定一个“启动”方法用于配置web应用,提供 IApplicationBuilder 对象用于配置。
- CaptureStartupErrors,设置是否要捕获启动时的错误。
- ConfigureKestrel,配置Kestrel的各种选项。
- ConfigureLogging,使用 ILoggingBuilder 进行 Logging 配置。
- PreferHostingUrls,指示主机是否应监听 IWebHostBuilder上配置的Url,而不是 IServer上配置的Url。
- SuppressStatusMessages,设置是否禁止启动状态消息。
- UseConfiguration,使用指定的配置。
- UseContentRoot,设置Host内容目录。
- UseDefaultServiceProvider,使用默认的服务提供程序,并进行配置。
- UseEnvironment,配置环境参数。
- UseHttpSys,将Http.sys指定为Host要使用的Web服务器。
- UseIIS,使用IISHttpServer替换Kestrel,并启用进程内(w3wp.exe 或 iisexpress.exe)模式。
- UseIISIntegration,使用IIS作为Kestrel的反向代理服务器。
- UseKestrel,指定Kestrel作为Web服务器。
- UseServer,为Host指定Web服务器,IServer。
- UseShutdownTimeout,设置 Host 的关闭超时时间。
- UseSockets,指定及配置Kestrel 传输使用的 Sockets。
- UseStartup,指定用于配置Web应用的Startup类型。
- UseStaticWebAssets,配置 WebRootFileProvider 使用由引用项目和程序包定义的静态Web资产文件。
- UseUrls,指定Host需要监听的Url。
- UseWebRoot,指定 Web 服务器使用的 Root 目录。
下面重点介绍IWebHostBuilder的扩展方法UseStartup。
5、Startup类
从上面的描述中,我们知道IWebHostBuilder提供了UseStartup扩展方法,用于配置Web相关的应用,返回IWebHostBuilder类型实例。Startup中默认定义了两个方法ConfigureServices和Configure。
(1)Startup的构造函数
在构造函数中,可以注入以下服务来使用:
-
IWebHostEnvironment
-
IHostEnvironment
-
IConfiguration
代码如下:
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment,IHostEnvironment hostEnvironment) { Configuration = configuration; WebHostEnvironment = webHostEnvironment; HostEnvironment1 = hostEnvironment; } public IConfiguration Configuration { get; } public IWebHostEnvironment WebHostEnvironment { get; } public IHostEnvironment HostEnvironment1 { get; }
(2)ConfigureServices
ConfigureServices是可选方法,并且会在Configure之前被主机调用。在 ConfigureServices
中注册服务,并通过依赖关系注入 (DI) 或 ApplicationServices 在整个应用中使用服务。该方法的作用就是将服务添加到服务容器,使这些服务可以在应用以及Configure方法中被使用。
public virtual void ConfigureServices (Microsoft.Extensions.DependencyInjection.IServiceCollection services);
这里重点提及IServiceCollection,先来看下IServiceCollection的命名空间:
Microsoft.Extensions.DependencyInjection
顾名思义,IServiceCollection和依赖注入有关,内部有DI容器相关的构造,具体原理这里不做详解。下面列下IServiceCollection 中的部分方法和扩展方法:
AddScoped,添加服务,服务实例的生命周期为Scoped。 AddTransient,添加服务,服务实例的生命周期为Transient(每次被使用都创建新对象)。 AddSingleton,添加服务,服务实例的生命周期为单例。 AddMvc,添加所有MVC所需的服务。 AddMvcCore,仅添加核心必要的MVC所需的服务。 AddControllers,添加启用Controller 所需要的服务,不包括View和Pages所需要的服务。 AddControllersWithViews,添加启用 Controller 以及 Razor 页面所需要的服务。 AddRazorPages,添加 Razor Pages 所需要的服务。 AddAntiforgery,添加防止CSRF攻击的服务。 AddAuthentication,添加启用Authentication中间件所需的服务。 AddAuthenticationCore,添加启用Authentication中间件所需的核心服务。 AddAuthorization,添加启用Authorization中间件所需的服务。 AddAuthorizationCore,添加启用Authorization中间件所需的核心服务。 AddAuthorizationPolicyEvaluator,添加 Authorization 策略评估服务。 AddCertificateForwarding,添加CertificateForwarding中间件所需的服务。 AddConnections,添加 http://ASP.NET Core Connection Handlers 所需的服务。 AddCors,添加CORS中间件 所需的服务。 AddDataProtection,添加 http://ASP.NET Core Data Protection 所需的服务。 AddDirectoryBrowser,添加 DirectoryBrowser 中间件所需的服务。 AddDistributedMemoryCache,添加分布式缓冲服务IDistributedCache,默认的实现将缓冲保存在内存中,要实现实际上的分布式缓冲你需要提供一个保存缓存的实现(Redis或数据库,如AddStackExchangeRedisCache和AddDistributedSqlServerCache)。 AddHealthChecks,添加HealthChecks中间件所需的服务。 AddHostedService,添加宿主服务,如持续运行的服务。 AddHostFiltering,添加HostFiltering中间件所需的服务。 AddHsts,添加HSTS中间件所需的服务。 AddHttpClient,添加IHttpClientFactory服务用于获取在服务器端发起Http请求的HttpClient对象。 AddHttpContextAccessor,添加Http上下文访问器服务,在例如Controller里有HttpContext属性的地方优先使用HttpContext,但如果在一个自定义的服务里你就需要IHttpContextAccessor服务来获取Http上下文。 AddHttpsRedirection,为HttpsRedirection中间件添加所需的服务。 AddIdentity,添加默认的身份系统,并制定 Role和User类型。 AddIdentityCore,添加默认身份执行的核心部分,并制定User类型。 AddLocalization,添加本地化服务。 AddLogging,添加日志服务。 AddMemoryCache,添加非分布式的内存缓存服务。 AddOptions,添加 Option 服务。 AddResponseCaching,为ResponseCaching中间件添加所需的服务。 AddResponseCompression,为ResponseCompression中间件添加所需的服务。 AddRouting,添加Routing中间件所需的服务。 AddSignalR,添加SignalR所需的服务。 AddSignalRCore,添加SignalR所需的核心服务。 AddServerSideBlazor,添加 Server-Side Blazor所需的服务。 AddWebEncoders,添加 HtmlEncoder,JavaScriptEncoder,UrlEncoder 三个服务。
(3)Configure
Configure 方法用于指定应用响应 HTTP 请求的方式,既配置Http请求处理管道。 具体方法是通过将中间件组件添加到 IApplicationBuilder 实例来配置请求处理管道。每个 Use 扩展方法会将一个或多个中间件组件添加到请求管道。 例如,UseStaticFiles 配置中间件提供静态文件。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
这里重点提及IApplicationBuilder ,不做详细介绍。下面介绍下IApplicationBuilder 常用的方法和扩展方法:
用于添加自定义中间件的方法: Use,添加一个中间件委托到应用的请求管道。 Run,添加一个请求管道的终点中间件,在该方法之后添加的中间件将不会被执行。 Map,该方法可根据请求路径为请求处理管道创建分支。当请求来自指定路径时运行分支请求处理管道。 UseWhen,基于提供的条件判断委托来决定是否将中间件添加到请求处理管道。 MapWhen,基于提供的条件判断委托来决定是否为请求处理管道创建分支。 UseMiddleware,将中间件类型添加到请求处理管道。 UseOwin,OWIN 为 Open Web Interface for .NET 的缩写,该方法可以为请求处理管道添加符合OWIN规范的中间件。
添加内建中间件的扩展方法: UseRequestLocalization,添加RequestLocalizationMiddleware,用于自动根据请求信息设置 Culture 信息。 UseAuthentication,添加AuthenticationMiddleware用于启用验证功能。 UseAuthorization,添加AuthorizationMiddleware用于启用授权功能。 UseCertificateForwarding,添加一个用于在请求中解码证书信息,并能将证书设置到HttpContext.Connection.ClientCertificate的中间件。 UseBlazorFrameworkFiles,配置应用程序以从指定的路径提供Blazor WebAssembly框架文件。 UseConcurrencyLimiter,添加一个并发限制中间件来显示执行并发请求的数量。 UseCookiePolicy,添加Cookie Policy 中间件来启用Cookie Policy 功能。 UseCors,添加一个 CORS 中间件来启用跨域请求的功能。 UseDatabaseErrorPage,捕获与数据相关的同步和异步的的异常,当此类异常发生时,一个带有可能解决这些异常信息的HMTL返回页面会被生成。(该方法已被标记为 Obsolete,在未来的版本中可能被移除)。 UseDefaultFiles,使用指定的配置进行默认文件映射。 UseDeveloperExceptionPage,该中间件在请求管道中捕获同步和异步的异常并生成供开发者查看的HTML错误页面。 UseDirectoryBrowser,使用指定的配置启用目录浏览功能。 UseEndpoints,添加 EndpointMiddleware 中间件,你可以通过IEndpointRouteBuilder对象进行配置,该中间件会执行与请求匹配的 Endpoint。 UseRouting,添加 EndpointRoutingMiddleware 中间件,该中间件的作用是将请求匹配到一个Endpoint。 UseExceptionHandler,使用指定的配置添加一个用于捕获异常的中间件,该中间件可以捕获异常,记录日志,并在另外一个请求处理管道重新执行请求,如果返回已经开始则不会重新执行请求。 UseFileServer,使用指定的配置启用所有的静态文件中间件(除了 DirectoryBrowser 中间件)。 UseForwardedHeaders,该中间件将转发的头信息应用到当前请求对应的字段。 具体来说,按照惯例 HTTP 代理通过众所周知的HTTP头信息转发来自客户端的信息。 该中间件读取这些头信息并填写HttpContext上的关联字段。 UseHeaderPropagation,添加一个中间件用于收集头信息并"传播"到HttpClient。 UseHealthChecks,使用指定的配置启用 HelthChecks 中间件。 UseHostFiltering,添加 HostFilter 中间件来过滤请求主机的头信息,不符合的请求会被拒绝并返回 400。 UseHsts,添加中间件来启用HSTS,该中间件会添加 Strict-Transport-Security 头信息。 UseHttpMethodOverride,该中间件可以将 POST 请求重写为其他 HTTP method, 重写后的 HTTP method 可在头信息或表单中指定(默认使用X-HTTP-Method-Override头信息),这个方法通常会在客户端只能发送GET或POST方法,但需要条用其他HTTP method时使用。 UseHttpsRedirection,添加一个中间件,可将HTTP的请求重定向到HTTPS。 UseMigrationsEndPoint,该中间件会检查当前的数据库是否有未被执行的 EF Core migrations,若有需要改中间件就会执行它,你可以在 MigrationsEndPointOptions.DefaultPath 来配置默认路径。 UseMvc,添加MVC及相关功能的中间件。 UseMvcWithDefaultRoute,添加MVC及相关功能的中间件,并使用名为“default”的Route,其模板为'{controller=Home}/{action=Index}/{id?}'。 UseResponseCaching,添加 ResponseCachingMiddleware 中间件来启用HTTP返回的缓存。 UseResponseCompression,添加一个可以动态压缩HTTP 返回的中间件。 UseRewriter,该中间件检查请求Url是否符合指定的规则和条件,并在满足条件时修改HttpContext。 UseRouter,添加 RouterMiddleware, 可指定一个 IRouter对象或Action<IRouteBuilder>。 UseSession,使用指定的配置添加 SessionMiddleware 中间件以为应用添加会话功能。 UseStaticFiles,使用配置将指定的路径作为静态文件目录。 UseStatusCodePages,添加可配置的 StatusCodePages 中间件,status code 为 400 到 599 之间并不带 body 的返回将由该中间件处理 UseStatusCodePagesWithRedirects,将 StatusCodePages 中间件添加到管道中。 与之前的 UseStatusCodePages 方法相比,该方法可以指定 location url。 UseStatusCodePagesWithReExecute,将 StatusCodePages 中间件添加到管道中。 与之前的 UseStatusCodePages 方法相比,该方法会要求以另一个路径重新执行请求处理管道。 UsePathBase,添加一个中间件,该中间件从请求路径中提取指定的Path Base,并将其后缀到请求的Path Base中。 UseWebAssemblyDebugging,启动WebAssembly调试,使Chromium调试工具可以调试Blazor WebAssembly应用。 UseWebSockets,使用指定的配置添加WebSockets中间件。 UseWelcomePage,使用指定的配置添加欢迎页面中间件。
需要注意的是,为了让上面某些中间件正常工作,你需要在 ConfigureServices 方法中添加这些中间件所需的服务。
六、ConfigureWebHost
先来看下IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)的源码:从ConfigureWebHostDefaults说起:
using System; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore; namespace Microsoft.Extensions.Hosting { public static class GenericHostBuilderExtensions { public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure) { if (configure is null) { throw new ArgumentNullException(nameof(configure)); } return builder.ConfigureWebHost(webHostBuilder => { WebHost.ConfigureWebDefaults(webHostBuilder); configure(webHostBuilder); }); } } }
在上边模块中也说道IHostBuilder.ConfigureWebHostDefaults()方法中会做两个处理,一个是WebHost.ConfigureWebDefaults(webHostBuilder)用于默认配置webhost。另一个就是接下来要介绍的IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure)。先来看下IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure)的源码:
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.Hosting { /// <summary> /// Contains extensions for an <see cref="IHostBuilder"/>. /// </summary> public static class GenericHostWebHostBuilderExtensions { /// <summary> /// Adds and configures an ASP.NET Core web application. /// </summary> /// <param name="builder">The <see cref="IHostBuilder"/> to add the <see cref="IWebHostBuilder"/> to.</param> /// <param name="configure">The delegate that configures the <see cref="IWebHostBuilder"/>.</param> /// <returns>The <see cref="IHostBuilder"/>.</returns> public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure) { if (configure is null) { throw new ArgumentNullException(nameof(configure)); } return builder.ConfigureWebHost(configure, _ => { }); } /// <summary> /// Adds and configures an ASP.NET Core web application. /// </summary> /// <param name="builder">The <see cref="IHostBuilder"/> to add the <see cref="IWebHostBuilder"/> to.</param> /// <param name="configure">The delegate that configures the <see cref="IWebHostBuilder"/>.</param> /// <param name="configureWebHostBuilder">The delegate that configures the <see cref="WebHostBuilderOptions"/>.</param> /// <returns>The <see cref="IHostBuilder"/>.</returns> public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureWebHostBuilder) { if (configure is null) { throw new ArgumentNullException(nameof(configure)); } if (configureWebHostBuilder is null) { throw new ArgumentNullException(nameof(configureWebHostBuilder)); } // Light up custom implementations namely ConfigureHostBuilder which throws. if (builder is ISupportsConfigureWebHost supportsConfigureWebHost) { return supportsConfigureWebHost.ConfigureWebHost(configure, configureWebHostBuilder); } var webHostBuilderOptions = new WebHostBuilderOptions(); configureWebHostBuilder(webHostBuilderOptions); var webhostBuilder = new GenericWebHostBuilder(builder, webHostBuilderOptions); configure(webhostBuilder); builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>()); return builder; } } }
可以看出,在ConfigureWebHost方法中,生成了类GenericWebHostBuilder的对象,同时也注入了GenericWebHostService服务。先来看下GenericWebHostBuilder定义:
public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options) { _builder = builder; var configBuilder = new ConfigurationBuilder() .AddInMemoryCollection(); if (!options.SuppressEnvironmentConfiguration) { configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_"); } _config = configBuilder.Build(); _builder.ConfigureHostConfiguration(config => { config.AddConfiguration(_config); // We do this super early but still late enough that we can process the configuration // wired up by calls to UseSetting ExecuteHostingStartups(); }); // IHostingStartup needs to be executed before any direct methods on the builder // so register these callbacks first _builder.ConfigureAppConfiguration((context, configurationBuilder) => { if (_hostingStartupWebHostBuilder != null) { var webhostContext = GetWebHostBuilderContext(context); _hostingStartupWebHostBuilder.ConfigureAppConfiguration(webhostContext, configurationBuilder); } }); _builder.ConfigureServices((context, services) => { var webhostContext = GetWebHostBuilderContext(context); var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; // Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting services.AddSingleton(webhostContext.HostingEnvironment); #pragma warning disable CS0618 // Type or member is obsolete services.AddSingleton((AspNetCore.Hosting.IHostingEnvironment)webhostContext.HostingEnvironment); services.AddSingleton<IApplicationLifetime, GenericWebHostApplicationLifetime>(); #pragma warning restore CS0618 // Type or member is obsolete services.Configure<GenericWebHostServiceOptions>(options => { // Set the options options.WebHostOptions = webHostOptions; // Store and forward any startup errors options.HostingStartupExceptions = _hostingStartupErrors; }); // REVIEW: This is bad since we don't own this type. Anybody could add one of these and it would mess things up // We need to flow this differently services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore")); services.TryAddSingleton<DiagnosticSource>(sp => sp.GetRequiredService<DiagnosticListener>()); services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore")); services.TryAddSingleton(DistributedContextPropagator.Current); services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>(); services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>(); services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>(); // IMPORTANT: This needs to run *before* direct calls on the builder (like UseStartup) _hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services); // Support UseStartup(assemblyName) 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 => { // Throw if there was any errors initializing startup capture.Throw(); }; }); } } }); }
GenericWebHostBuilder的构造函数接收IHostBuilder作为参数,其实是把asp.netcore用到的服务和配置加载到IHostBuilder中。配置完成后调用UseStartup方法,UseStartup定义如下:
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 { // We cannot support methods that return IServiceProvider as that is terminal and we need ConfigureServices to compose if (typeof(IStartup).IsAssignableFrom(startupType)) { throw new NotSupportedException($"{typeof(IStartup)} isn't supported"); } if (StartupLoader.HasConfigureServicesIServiceProviderDelegate(startupType, context.HostingEnvironment.EnvironmentName)) { throw new NotSupportedException($"ConfigureServices returning an {typeof(IServiceProvider)} isn't supported."); } 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); configureServices(services); // REVIEW: We're doing this in the callback so that we have access to the hosting environment // Startup.ConfigureContainer var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName); if (configureContainerBuilder.MethodInfo != null) { var containerType = configureContainerBuilder.GetContainerType(); // Store the builder in the property bag _builder.Properties[typeof(ConfigureContainerBuilder)] = configureContainerBuilder; var actionType = typeof(Action<,>).MakeGenericType(typeof(HostBuilderContext), containerType); // Get the private ConfigureContainer method on this type then close over the container type 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 }); } // Resolve Configure after calling ConfigureServices and ConfigureContainer 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(); // Execute Startup.Configure if (instance != null && configureBuilder != null) { configureBuilder.Build(instance)(app); } }; }); }
从上面代码中可以看出,先调用ConfigureServices方法注册服务,再调用ConfigureContainer方法配置容器,最后调用Configure方法配置请求链。我们再看下GenericWebHostService类中的StartAsync方法,定义如下:
public async Task StartAsync(CancellationToken cancellationToken) { HostingEventSource.Log.HostStart(); var serverAddressesFeature = Server.Features.Get<IServerAddressesFeature>(); var addresses = serverAddressesFeature?.Addresses; if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0) { var urls = Configuration[WebHostDefaults.ServerUrlsKey]; if (!string.IsNullOrEmpty(urls)) { serverAddressesFeature!.PreferHostingUrls = WebHostUtilities.ParseBool(Configuration, WebHostDefaults.PreferHostingUrlsKey); foreach (var value in urls.Split(';', StringSplitOptions.RemoveEmptyEntries)) { addresses.Add(value); } } } RequestDelegate? application = null; try {
//1、从GenericWebHostServiceOptions的ConfigureApplication属性中获取RequestDelegate委托链
var configure = Options.ConfigureApplication; 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."); } //2、调用IApplicationBuilderFactory接口的CreateBuilder创建IApplicationBuilder
var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features);
//3、调用IStartupFilter的Configure方法配置整个委托链
foreach (var filter in StartupFilters.Reverse()) { configure = filter.Configure(configure); } configure(builder); //4、Build the request pipeline,调用IApplicationBuilder方法构建最终的RequestDelegate application = builder.Build(); } catch (Exception ex) { Logger.ApplicationError(ex); if (!Options.WebHostOptions.CaptureStartupErrors) { throw; } var showDetailedErrors = HostingEnvironment.IsDevelopment() || Options.WebHostOptions.DetailedErrors; application = ErrorPageBuilder.BuildErrorPageApplication(HostingEnvironment.ContentRootFileProvider, Logger, showDetailedErrors, ex); } //5、创建HostingApplication对象 var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, ActivitySource, Propagator, HttpContextFactory); //6、调用IServer接口的StartAsync开始处理请求 await Server.StartAsync(httpApplication, cancellationToken); if (addresses != null) { foreach (var address in addresses) { Log.ListeningOnAddress(LifetimeLogger, address); } } if (Logger.IsEnabled(LogLevel.Debug)) { foreach (var assembly in Options.WebHostOptions.GetFinalHostingStartupAssemblies()) { Log.StartupAssemblyLoaded(Logger, assembly); } } if (Options.HostingStartupExceptions != null) { foreach (var exception in Options.HostingStartupExceptions.InnerExceptions) { Logger.HostingStartupAssemblyError(exception); } } }
综上所述,由于ASP.NET Core应用是由GenericWebHostService服务托管的,所以启动应用程序本质上就是启动这个托管服务。托管服务GenericWebHostService在启动过程中的处理流程基本上体现在如下所示的StartAsync方法中,StartAsync方法中的调用流程如下:
-
从GenericWebHostServiceOptions的ConfigureApplication属性中获取RequestDelegate委托链
-
然后调用IApplicationBuilderFactory接口的CreateBuilder创建IApplicationBuilder
-
再调用IStartupFilter的Configure方法配置整个委托链
-
接着调用IApplicationBuilder方法构建最终的RequestDelegate
-
接着创建HostingApplication对象
-
最后调用IServer接口的StartAsync开始处理请求