目的
了解.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终于运行起来了。