先看下如下的扩展方法
public static class GenericHostWebHostBuilderExtensions { 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; } }
当我们调用IHostBuilder的扩展方法ConfigureWebHost时候,系统创建了GenericWebHostBuilder类,并注册GenericWebHostService类
再分析下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(); }; }); } } }); }
GenericWebHostBuilder的构造函数接收IHostBuilder作为参数,其实是把asp.netcore用到的服务和配置加载到IHostBuilder中
添加一“ASPNETCORE_”开头的环境变量
寻找程序集有HostingStartupAttribute的特性,调用IHostingStartup接口对IWebHostBuilder进行配置
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>(); services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>(); services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
接下来调用实现IStartUp接口的类
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); } }; }); }
先找到ConfigureServices方法注册服务
再找到ConfigureContainer方法配置容器
最后找到Configure方法配置请求链
我们再看下GenericWebHostService类
internal class GenericWebHostService : IHostedService { public GenericWebHostService(IOptions<GenericWebHostServiceOptions> options, IServer server, ILoggerFactory loggerFactory, DiagnosticListener diagnosticListener, IHttpContextFactory httpContextFactory, IApplicationBuilderFactory applicationBuilderFactory, IEnumerable<IStartupFilter> startupFilters, IConfiguration configuration, IWebHostEnvironment hostingEnvironment) { Options = options.Value; Server = server; Logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Hosting.Diagnostics"); LifetimeLogger = loggerFactory.CreateLogger("Microsoft.Hosting.Lifetime"); DiagnosticListener = diagnosticListener; HttpContextFactory = httpContextFactory; ApplicationBuilderFactory = applicationBuilderFactory; StartupFilters = startupFilters; Configuration = configuration; HostingEnvironment = hostingEnvironment; } public GenericWebHostServiceOptions Options { get; } public IServer Server { get; } public ILogger Logger { get; } // Only for high level lifetime events public ILogger LifetimeLogger { get; } public DiagnosticListener DiagnosticListener { get; } public IHttpContextFactory HttpContextFactory { get; } public IApplicationBuilderFactory ApplicationBuilderFactory { get; } public IEnumerable<IStartupFilter> StartupFilters { get; } public IConfiguration Configuration { get; } public IWebHostEnvironment HostingEnvironment { get; } 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); } } } private RequestDelegate BuildErrorPageApplication(Exception exception) { if (exception is TargetInvocationException tae) { exception = tae.InnerException; } var showDetailedErrors = HostingEnvironment.IsDevelopment() || Options.WebHostOptions.DetailedErrors; var model = new ErrorPageModel { RuntimeDisplayName = RuntimeInformation.FrameworkDescription }; var systemRuntimeAssembly = typeof(System.ComponentModel.DefaultValueAttribute).GetTypeInfo().Assembly; var assemblyVersion = new AssemblyName(systemRuntimeAssembly.FullName).Version.ToString(); var clrVersion = assemblyVersion; model.RuntimeArchitecture = RuntimeInformation.ProcessArchitecture.ToString(); var currentAssembly = typeof(ErrorPage).GetTypeInfo().Assembly; model.CurrentAssemblyVesion = currentAssembly .GetCustomAttribute<AssemblyInformationalVersionAttribute>() .InformationalVersion; model.ClrVersion = clrVersion; model.OperatingSystemDescription = RuntimeInformation.OSDescription; model.ShowRuntimeDetails = showDetailedErrors; if (showDetailedErrors) { var exceptionDetailProvider = new ExceptionDetailsProvider( HostingEnvironment.ContentRootFileProvider, sourceCodeLineCount: 6); model.ErrorDetails = exceptionDetailProvider.GetDetails(exception); } else { model.ErrorDetails = new ExceptionDetails[0]; } var errorPage = new ErrorPage(model); return context => { context.Response.StatusCode = 500; context.Response.Headers[HeaderNames.CacheControl] = "no-cache"; context.Response.ContentType = "text/html; charset=utf-8"; return errorPage.ExecuteAsync(context); }; } public async Task StopAsync(CancellationToken cancellationToken) { try { await Server.StopAsync(cancellationToken); } finally { HostingEventSource.Log.HostStop(); } } }
看下核心方法StartAsync
从GenericWebHostServiceOptions的ConfigureApplication属性中获取RequestDelegate委托链
调用IApplicationBuilderFactory接口的CreateBuilder创建IApplicationBuilder
再调用IStartupFilter的Configure方法配置整个委托链
接着调用IApplicationBuilder方法构建最终的RequestDelegate
接着创建HostingApplication对象
最后调用IServer接口的StartAsync开始处理请求