目的

了解.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方法。

 

第二处的代码就是实际运行



var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, HttpContextFactory);

await Server.StartAsync(httpApplication, cancellationToken);


 

首先创建HostingApplication实例,然后运行Server.StartAsync()。这里的Server就是前面的IServer,根据Hosting的设置,可能是KestrelServer或IISHttpServer。至此,我们的Host终于运行起来了。