.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
  • Host会等待所有的IHostedService停止
posted @ 2021-07-23 14:08  老男人更当自强  阅读(739)  评论(0编辑  收藏  举报