.NET Core通用Host源码分析
目的
了解.NET Core通用Host的创建以及运行的过程,并且了解StartUp的两个方法ConfigureServices以及Configure如何参与到Host的创建与运行过程当中。
本文中的源码来自.NET Core 3.1,新版本的源码可能会有一些不同。
.NET Core 项目模板
让我们从标准的.NET Core项目模板开始
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>(); }); }
可以看到Host的创建和运行有以下几个步骤
- Host.CreateDefaultBuilder()
- ConfigureWebHostDefaults()
- Build()
- Run()
那么接下来,我们对它们进行一一探索
Host.CreateDefaultBuilder()
这个方法的完整代码如下
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) => { IHostEnvironment env = hostingContext.HostingEnvironment; bool reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true); config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange); 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) => { bool 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(); } logging.Configure(options => { options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId | ActivityTrackingOptions.TraceId | ActivityTrackingOptions.ParentId; }); }) .UseDefaultServiceProvider((context, options) => { bool isDevelopment = context.HostingEnvironment.IsDevelopment(); options.ValidateScopes = isDevelopment; options.ValidateOnBuild = isDevelopment; }); return builder; }
可以看到,它首先是生成了一个HostBuilder实例,然后一系列的方法来配置
- ContentRoot
- Host Configuration
-
- 最终保存在_configureAppConfigActions变量中
- App Configuration
-
- 最终保存在_configureAppConfigActions变量中
- Logging
-
- 在依赖注入容器中添加服务
-
-
- ILoggerFactory (LoggerFactory)
- ILogger<> (Logger<>)
- IConfigOptions<LoggerFilterOptions> (DefaultLoggerLevelConfigureOptions)
-
-
- 添加以下LoggerProvider
-
-
- ConsoleLoggerProvider
- DebugLoggerProvider
- EventSourceLoggerProvider
-
- 缺省的ServiceProvider (也就是依赖注入的容器)
最后返回新建的HostBuilder实例
ConfigureWebHostDefault()
这个方法的完整代码如下
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); }); }
调用hostBuilder的ConfigureWebHost方法,那我们来看看这个方法
public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure) { var webhostBuilder = new GenericWebHostBuilder(builder); configure(webhostBuilder); builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>()); return builder; }
这里的configure()方法,结合前面的ConfigureWebHostDefaults(),以及在我们的Program中的代码,等于如下的代码
WebHost.ConfigureWebDefaults(webHostBuilder);
webHostBuilder.UseStartup<Startup>();
所以ConfigureWebHost是按照下面的顺序进行了调用
- new GenericWebHostBuilder()
- WebHost.ConfigureWebDefault()
- webHostBuilder.UseStartup<Startup>()
- services.AddHostedService<GenericWebHostService>()
我们来分别看看它们各自都做了什么
new GenericWebHostBuilder()
这个类的构造器源码如下
public GenericWebHostBuilder(IHostBuilder builder) { _builder = builder; _config = new ConfigurationBuilder() .AddEnvironmentVariables(prefix: "ASPNETCORE_") .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 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>(); // 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(); }; }); } } }); }
这个类在原来HostBuilder的基础之上,额外添加了针对Web的一些配置
WebHost.ConfigureWebDefault()
它的源码如下
internal static void ConfigureWebDefaults(IWebHostBuilder builder) { builder.ConfigureAppConfiguration((ctx, cb) => { if (ctx.HostingEnvironment.IsDevelopment()) { StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration); } }); builder.UseKestrel((builderContext, options) => { options.Configure(builderContext.Configuration.GetSection("Kestrel")); }) .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[] { "*" }); } }); // Change notification services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>( new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase)) { services.Configure<ForwardedHeadersOptions>(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; // Only loopback proxies are allowed by default. Clear that restriction because forwarders are // being enabled by explicit configuration. options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>(); } services.AddRouting(); }) .UseIIS() .UseIISIntegration(); }
这个方法再次添加AppConfiguration,然后添加Kestrel和IIS,这里IIS有两个调用,UseIIS和UseIISIntegration,它们分别对应了IIS的两种模式:InProcess和OutOfProcess。
UseKestrel()
public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder, Action<KestrelServerOptions> options) { return hostBuilder.UseKestrel().ConfigureKestrel(options); } public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder) { return hostBuilder.ConfigureServices(services => { // Don't override an already-configured transport services.TryAddSingleton<IConnectionListenerFactory, SocketTransportFactory>(); services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>(); services.AddSingleton<IServer, KestrelServer>(); }); }
UseKestrel()添加了KestrelServer,也就是内置的Web服务器。
UseIIS()
public static IWebHostBuilder UseIIS(this IWebHostBuilder hostBuilder) { if (hostBuilder == null) { throw new ArgumentNullException(nameof(hostBuilder)); } // Check if in process if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && NativeMethods.IsAspNetCoreModuleLoaded()) { var iisConfigData = NativeMethods.HttpGetApplicationProperties(); // Trim trailing slash to be consistent with other servers var contentRoot = iisConfigData.pwzFullApplicationPath.TrimEnd(Path.DirectorySeparatorChar); hostBuilder.UseContentRoot(contentRoot); return hostBuilder.ConfigureServices( services => { services.AddSingleton(new IISNativeApplication(iisConfigData.pNativeApplication)); services.AddSingleton<IServer, IISHttpServer>(); services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(iisConfigData.pwzVirtualApplicationPath)); services.AddAuthenticationCore(); services.AddSingleton<IServerIntegratedAuth>(_ => new ServerIntegratedAuth() { IsEnabled = iisConfigData.fWindowsAuthEnabled || iisConfigData.fBasicAuthEnabled, AuthenticationScheme = IISServerDefaults.AuthenticationScheme }); services.Configure<IISServerOptions>( options => { options.ServerAddresses = iisConfigData.pwzBindings.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); options.ForwardWindowsAuthentication = iisConfigData.fWindowsAuthEnabled || iisConfigData.fBasicAuthEnabled; options.IisMaxRequestSizeLimit = iisConfigData.maxRequestBodySize; } ); }); } return hostBuilder; } internal const string AspNetCoreModuleDll = "aspnetcorev2_inprocess.dll"; public static bool IsAspNetCoreModuleLoaded() { return GetModuleHandle(AspNetCoreModuleDll) != IntPtr.Zero; }
UseIIS()会检测是不是Windows操作系统,并且NativeMethods.IsAspNetCoreModuleLoaded()会检测是不是加载了这个特殊的dll (aspnetcorev2_inprocess.dll),如果都是的话,注册IISHttpServer服务,覆盖之前Kestrel服务的注册。
webHostBuilder.UseStartup<Startup>()
这个方法的调用源码
public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class { return hostBuilder.UseStartup(typeof(TStartup)); } public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) { var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName); // Light up the GenericWebHostBuilder implementation if (hostBuilder is ISupportsStartup supportsStartup) { return supportsStartup.UseStartup(startupType); } return hostBuilder .ConfigureServices(services => { if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) { services.AddSingleton(typeof(IStartup), startupType); } else { services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>(); return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)); }); } }); }
首先检测当前的IWebHostBuilder是不是支持ISupportStartup,而当前的实例GenericWebHostBuilder是支持这个接口的,所以 supportsStartup.UseStartup(startupType) 实际上是调用的GenericWebHostBuilder的UseStartup()方法。我们来看看它的源码
public IWebHostBuilder UseStartup(Type startupType) { // UseStartup can be called multiple times. Only run the last one. _builder.Properties["UseStartup.StartupType"] = startupType; _builder.ConfigureServices((context, services) => { if (_builder.Properties.TryGetValue("UseStartup.StartupType", out var cachedType) && (Type)cachedType == startupType) { UseStartup(startupType, context, services); } }); return this; } private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services) { var webHostBuilderContext = GetWebHostBuilderContext(context); var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; ExceptionDispatchInfo startupError = null; object instance = 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 = GetType().GetMethod(nameof(ConfigureContainer), BindingFlags.NonPublic | BindingFlags.Instance) .MakeGenericMethod(containerType) .CreateDelegate(actionType, this); // _builder.ConfigureContainer<T>(ConfigureContainer); typeof(IHostBuilder).GetMethods().First(m => m.Name == 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); } }; }); }
它大致的流程
- 使用ActivatorUtilities生成当前Startup的一个实例,
- 使用StartupLoader.FindConfigureServicesDelegate()来找到我们的Startup的ConfigureServices()方法,然后Build()它,并且调用之。
- 使用StartupLoader.FindConfigureContainerDelegate()来找到Startup中的ConfigureContainer()方法,这个方法在我们需要使用自定义的依赖注入容器的时候需要
- 使用StartupLoader.FindConfigureDelete()来找到Startup中的Configure()方法,这个方法最终被放到了GenericWebHostServiceOptions的ConfigureApplication中
为什么需要用StartupLoader来寻找这些方法,并且调用呢?原因是在Configure()方法中我们可以注入其它的依赖,例如我们可以注入额外的IWebHostEnvironment
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
事实上,除了.NET Core提供的系统服务外,我们可以注入任何在ConfigureServices()中注册的服务。
到了这里,我们已经看到我们的Startup类的两个方法ConfigureServices()已经被调用,而Configure()方法被放置在GenericWebHostServiceOptions的ConfigureApplication中等待接下来的调用。
Build()
HostBuilder的Build方法的源码如下
public IHost Build() { if (_hostBuilt) { throw new InvalidOperationException("Build can only be called once."); } _hostBuilt = true; BuildHostConfiguration(); CreateHostingEnvironment(); CreateHostBuilderContext(); BuildAppConfiguration(); CreateServiceProvider(); return _appServices.GetRequiredService<IHost>(); }
它调用了一些列的方法,
- BuildHostConfiguration()
- CreateHostingEnvironment()
- CreateHostBuilderContext()
- BuildAppConfiguration()
- CreateServiceProvider()
最后返回从容器中获取IHost的服务。
下面我们再一个一个的看看它们都在做什么。
BuildHostConfiguration()
private void BuildHostConfiguration() { IConfigurationBuilder configBuilder = new ConfigurationBuilder() .AddInMemoryCollection(); // Make sure there's some default storage since there are no default providers foreach (Action<IConfigurationBuilder> buildAction in _configureHostConfigActions) { buildAction(configBuilder); } _hostConfiguration = configBuilder.Build(); }
它依次调用之前保存在_configureHostConfigActions的delegate(也就是之前的各个ConfigureHostApplication调用),最后生成_hostConfiguration。
CreateHostingEnvironment()
private void CreateHostingEnvironment() { _hostingEnvironment = new HostingEnvironment() { ApplicationName = _hostConfiguration[HostDefaults.ApplicationKey], EnvironmentName = _hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production, ContentRootPath = ResolveContentRootPath(_hostConfiguration[HostDefaults.ContentRootKey], AppContext.BaseDirectory), }; if (string.IsNullOrEmpty(_hostingEnvironment.ApplicationName)) { // Note GetEntryAssembly returns null for the net4x console test runner. _hostingEnvironment.ApplicationName = Assembly.GetEntryAssembly()?.GetName().Name; } _hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath); }
它根据之前的_hostConfiguration,生成一个HostingEnvironment的实例。
CreateHostBuilderContext
private void CreateHostBuilderContext() { _hostBuilderContext = new HostBuilderContext(Properties) { HostingEnvironment = _hostingEnvironment, Configuration = _hostConfiguration }; }
它把之前的_hostingEnvironment以及_hostConfiguration包装成一个HostBuilderContext。
BuildAppConfiguration()
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; }
它首先依次调用保存在_configureAppConfigActions中的delegate(也就是之前的各个ConfigureAppHosting调用),然后生成_appConfiguration。最后把_appConfiguration放到_hostBuilderContext的Configuration中,可以看到_hostBuilderContext.Configuration在调用BuildAppConfiguration之后被替换成了_appConfiguration。
CreateServiceProvider()
private void CreateServiceProvider() { var services = new ServiceCollection(); #pragma warning disable CS0618 // Type or member is obsolete services.AddSingleton<IHostingEnvironment>(_hostingEnvironment); #pragma warning restore CS0618 // Type or member is obsolete services.AddSingleton<IHostEnvironment>(_hostingEnvironment); services.AddSingleton(_hostBuilderContext); // register configuration as factory to make it dispose with the service provider services.AddSingleton(_ => _appConfiguration); #pragma warning disable CS0618 // Type or member is obsolete services.AddSingleton<IApplicationLifetime>(s => (IApplicationLifetime)s.GetService<IHostApplicationLifetime>()); #pragma warning restore CS0618 // Type or member is obsolete services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>(); services.AddSingleton<IHostLifetime, ConsoleLifetime>(); services.AddSingleton<IHost, Internal.Host>(); services.AddOptions(); services.AddLogging(); foreach (Action<HostBuilderContext, IServiceCollection> configureServicesAction in _configureServicesActions) { configureServicesAction(_hostBuilderContext, services); } object containerBuilder = _serviceProviderFactory.CreateBuilder(services); foreach (IConfigureContainerAdapter containerAction in _configureContainerActions) { containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder); } _appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder); if (_appServices == null) { throw new InvalidOperationException($"The IServiceProviderFactory returned a null IServiceProvider."); } // resolve configuration explicitly once to mark it as resolved within the // service provider, ensuring it will be properly disposed with the provider _ = _appServices.GetService<IConfiguration>(); }
它首先生成一个ServiceCollection的实例,ServiceCollection是.NET Core中轻量级的依赖注入的一部分,主要用来保存各类服务的注册(ServiceDescriptor)。
然后添加了一些系统的服务,IHostingEnvironment,IConfiguration, IHostLifetime, IHost等等。
之后便依次调用保存在_configureServicesActions的delegate,也就是之前的各类ConfigureServices,当然也包括在Starup中的ConfigureServices()。
然后调用_serviceProviderFactory.CreateBuilder()生成一个container builder,这里是.NET Core允许第三方依赖注入库的地方,.NET Core提供的缺省的实现是DefaultServiceProviderFactory。
最后调用_serviceProviderFactory.CreateServiceProvider()生成_appServices。
在Build()方法的最后,从_appServices中获取IHost,然后返回,从上面的CreateServiceProvider()的源码可以看到,它对应的是Microsoft.Extensions.Hosting.Internal.Host。
Run()
在上面Build()完之后,返回一个IHost的实例,这个Run()是IHost的一个扩展方法,而RunAsync()又是IHost的另外一个扩展方法。
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(); } } }
RunAsync()调用StartAsync(),然后调用WaitForShutdownAsync()等待它结束。我们知道IHost对应的实例是Microsoft.Extensions.Hosting.Internal.Host,我们来看看它的StartAsync()方法。
public async Task StartAsync(CancellationToken cancellationToken = default) { _logger.Starting(); using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping); CancellationToken combinedCancellationToken = combinedCancellationTokenSource.Token; await _hostLifetime.WaitForStartAsync(combinedCancellationToken).ConfigureAwait(false); combinedCancellationToken.ThrowIfCancellationRequested(); _hostedServices = Services.GetService<IEnumerable<IHostedService>>(); foreach (IHostedService hostedService in _hostedServices) { // Fire IHostedService.Start await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false); } // Fire IHostApplicationLifetime.Started _applicationLifetime.NotifyStarted(); _logger.Started(); }
它核心的代码就是从容器中获取所有的IHostedService服务,然后依次调用StartAsync()。在前面的ConfigureWebHost()的方法中有一行代码,
services.AddHostedService<GenericWebHostService>();
通过AddHostedService的源码,我们可以看到它其实添加了一个IHostedService服务
public static IServiceCollection AddHostedService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services) where THostedService : class, IHostedService { services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>()); return services; }
所以在Host的StartAsync()方法最后调用了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(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) { addresses.Add(value); } } } RequestDelegate application = null; try { Action<IApplicationBuilder> 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."); } var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features); foreach (var filter in StartupFilters.Reverse()) { configure = filter.Configure(configure); } configure(builder); // Build the request pipeline 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); if (addresses != null) { foreach (var address in addresses) { LifetimeLogger.LogInformation("Now listening on: {address}", address); } } if (Logger.IsEnabled(LogLevel.Debug)) { foreach (var assembly in Options.WebHostOptions.GetFinalHostingStartupAssemblies()) { Logger.LogDebug("Loaded hosting startup assembly {assemblyName}", assembly); } } if (Options.HostingStartupExceptions != null) { foreach (var exception in Options.HostingStartupExceptions.InnerExceptions) { Logger.HostingStartupAssemblyError(exception); } } }
它的代码比较长,但是核心代码只有两处。
第一处是整个中间件处理管道的建立
Action<IApplicationBuilder> 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."); } var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features); foreach (var filter in StartupFilters.Reverse()) { configure = filter.Configure(configure); } configure(builder); // Build the request pipeline application = builder.Build();
首先先从Options中获取ConfigureApplication,如果大家对前面的代码还有印象的话,那就是我们的Configure方法。然后按相反的次序,对所有的StartupFilters调用Configure,最终调用Build()生成我们的应用。
第二处的代码就是实际运行
var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, HttpContextFactory); await Server.StartAsync(httpApplication, cancellationToken);
首先创建HostingApplication实例,然后运行Server.StartAsync()。这里的Server就是前面的IServer,根据Hosting的设置,可能是KestrelServer或IISHttpServer。至此,我们的Host终于运行起来了。
总结
如果不按照程序代码的顺序,整个Host的创建过程是按照下面的顺序
- 创建和配置Host Configuration
- 创建HostingEnvironment实例
- 创建HostingBuilderContext实例
- 其实就包装HostingEnvironment和Configuration
- 创建和配置App Configuration
- 并且替换掉HostingBuilderContext的Configuration
- 创建一个ServiceCollection实例
- 然后先注册系统的服务
- IHostingEnvironment/IHostEnvironment
- HostingBuilderContext
- App Configuration
- IApplicationLifeTime/IHostApplicationLifetime
- IHostLifetime
- IHost
- Options
- Logging
- 然后调用我们自己的配置服务的方法
- 这里包括Startup中的ConfigureServices()
- 然后先注册系统的服务
- 然后生成依赖注入的容器
- 然后从容器中获取IHost的实例,
- 然后开始运行IHost
- 从容器中获取所有的IHostedService
- 然乎依次调用StartAsync
- GenericWebHostService的StartAsync
- 首先生成Middleware Pipeline,这里包括了Startup中的Configure()方法
- 然后创建HostingApplication,并且调用IServer(可能是KestrelServer或IISHttpServer)来运行HostingApplication
- GenericWebHostService的StartAsync
- Host会等待所有的IHostedService停止