Asp.Net Core启动流程
起因
昨天修改博客程序,发现有部分代码是Asp.Net Core 2.x的用法,博客程序从Mono+Nancy时期到Asp.Net Core,只是简单调整了代码.早期为了调试方便,使用不少黑科技.比如预处理.在改为Asp.Net Core 3.x的时候,还是没有调整启动相关的代码.后来使用Jexus代理.有问题不好调试,便修改默认的启动代码,改为从配置文件读取端口.正式这一块的代码才想起来看Asp.Net Core启动流程相关的代码.
在C#中使用预处理,不过这种方式还是少使用
#if RELEASE
//正式环境
IWebHostBuilder hostBuilder = null;
hostBuilder = new WebHostBuilder()
.UseKestrel()
.ConfigureAppConfiguration((hostingContext, config) =>
{
//读取配置文件
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true);
})
.ConfigureKestrel((context, options) =>
{
//读取配置文件端口
var basePort = context.Configuration.GetValue<int?>("BASE_PORT") ?? 5000;
options.Listen(IPAddress.Any, basePort, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
});
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>();
hostBuilder.Build().Run();
#elif DEBUG
//本地环境,方便调试
var hostBuilder = CreateHostBuilder(args);
hostBuilder.Build().Run();
#endif
Asp.Net Core和Asp.Net宿主区别
在Asp.Net时代,宿主在Windows上首选便是IIS,在Linux上使用Jexus,当然还有XSP,这个没有用过.换到Asp.Net Core自带的宿主便是Kestrel,所以在流程便改为:
xxx.exe运行→Kestrel→Startup→中间件→MVC/Web API
在创建项目后看不到Kestrel相关的代码,是因为又封装了,早期是可以看到的.在上方代码中是可以调用UseKestrel,这是配置Kestrel,紧接着ConfigureKestrel配置Kestrel监听端口,如果不指定端口,会默认使用5000(http)和5001(https).我们项目用IIS/Jexus代理后,其实没有必要使用5001端口,或者开启https的.Kestrel只需要做一个安静处理请求的美男子就好了.
我们在看一下默认生成的代码并看一下代码简易流程图:
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.CreateHostBuilder代码:
//代码没有在Asp.Net Core项目中, 而是在Microsoft.Extensions.Hosting(在Dotnet/Runtime/libraries
//github地址:https://github.com/dotnet/runtime/Microsoft.Extensions.Hosting)中
//剔除一些代码注释
namespace Microsoft.Extensions.Hosting
{
public static class Host
{
public static IHostBuilder CreateDefaultBuilder() =>
CreateDefaultBuilder(args: null);
public static IHostBuilder CreateDefaultBuilder(string[] args)
{
//创建HostBuilder
var builder = new HostBuilder();
//获取当前程序所在目录
builder.UseContentRoot(Directory.GetCurrentDirectory());
//配置host
builder.ConfigureHostConfiguration(config =>
{
config.AddEnvironmentVariables(prefix: "DOTNET_");
if (args != null)
{
config.AddCommandLine(args);
}
});
//获取程序所需的配置,如appsettings.json
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
var 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,会将内部的配置覆盖
.ConfigureLogging((hostingContext, logging) =>
{
var 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();
}
})
//使用默认的服务,初步怀疑是容器相关的,内部实例化ServiceProviderOptions和DefaultServiceProviderFactory(serviceProviderOptions)
//在CreateServiceProvider内部调用BuildServiceProvider(IServiceCollection扩展方法),
.UseDefaultServiceProvider((context, options) =>
{
var isDevelopment = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
return builder;
}
}
}
ConfigureWebHostDefaults源码:
//
public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)
{
return builder.ConfigureWebHost(webHostBuilder =>
{
WebHost.ConfigureWebDefaults(webHostBuilder); //
configure(webHostBuilder);
});
}
ConfigureWebHost代码:
public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure)
{
var webhostBuilder = new GenericWebHostBuilder(builder); //GenericWebHostBuilder实现IWebHostBuilder
configure(webhostBuilder);
//在GenericWebHostService注册到容器中,在实例化的时候会实例化KestrelServer
builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
return builder;
}
WebHost.ConfigureWebDefaults代码:
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((ctx, cb) =>
{
if (ctx.HostingEnvironment.IsDevelopment())
{
StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
}
});
//终于找到你了,在UseKestrel内部把SocketTransportFactory/KestrelServerOptionsSetup/KestrelServer添加到容器中
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() // IIS相关的中间件
.UseIISIntegration(); // 与IIS集成的中间件
}
接下来我们Build方法中都有什么.
public IHost Build()
{
if (_hostBuilt)
{
throw new InvalidOperationException("Build can only be called once.");
}
_hostBuilt = true;
BuildHostConfiguration();
CreateHostingEnvironment();
CreateHostBuilderContext();
BuildAppConfiguration();
CreateServiceProvider(); //这里创建了 di的容器
return _appServices.GetRequiredService<IHost>(); //从di的容器获取Host实例,在Run函数内部调用
}
private void BuildHostConfiguration()
{
var configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection(); // Make sure there's some default storage since there are no default providers
foreach (var buildAction in _configureHostConfigActions)
{
buildAction(configBuilder);
}
_hostConfiguration = configBuilder.Build();
}
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);
}
private string ResolveContentRootPath(string contentRootPath, string basePath)
{
if (string.IsNullOrEmpty(contentRootPath))
{
return basePath;
}
if (Path.IsPathRooted(contentRootPath))
{
return contentRootPath;
}
return Path.Combine(Path.GetFullPath(basePath), contentRootPath);
}
private void CreateHostBuilderContext()
{
_hostBuilderContext = new HostBuilderContext(Properties)
{
HostingEnvironment = _hostingEnvironment,
Configuration = _hostConfiguration
};
}
private void BuildAppConfiguration()
{
var configBuilder = new ConfigurationBuilder()
.SetBasePath(_hostingEnvironment.ContentRootPath)
.AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);
foreach (var buildAction in _configureAppConfigActions)
{
buildAction(_hostBuilderContext, configBuilder);
}
_appConfiguration = configBuilder.Build();
_hostBuilderContext.Configuration = _appConfiguration;
}
private void CreateServiceProvider()
{
// Asp.Net Core 自带的容器在这里初始化 new ServiceCollection
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>(); // 在这里将Host添加到容器 在Main方法Build函数调用返回的,会获取容器中获取Host实例
services.AddOptions();
services.AddLogging();
foreach (var configureServicesAction in _configureServicesActions)
{
configureServicesAction(_hostBuilderContext, services);
}
var containerBuilder = _serviceProviderFactory.CreateBuilder(services);
foreach (var 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>();
}
最后看一下Run内部都有什么黑科技.
Run函数在HostingAbstractionsHostExtensions.cs文件中,可以看到又是通过扩展方法往Host对象添加函数.
/// <summary>
/// Runs an application and block the calling thread until host shutdown.
/// </summary>
/// <param name="host">The <see cref="IHost"/> to run.</param>
public static void Run(this IHost host)
{
host.RunAsync().GetAwaiter().GetResult();
}
/// <summary>
/// Runs an application and returns a Task that only completes when the token is triggered or shutdown is triggered.
/// </summary>
/// <param name="host">The <see cref="IHost"/> to run.</param>
/// <param name="token">The token to trigger shutdown.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
public static async Task RunAsync(this IHost host, CancellationToken token = default)
{
try
{
await host.StartAsync(token);
await host.WaitForShutdownAsync(token);
}
finally
{
if (host is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
host.Dispose();
}
}
}
/// <summary>
/// Returns a Task that completes when shutdown is triggered via the given token.
/// </summary>
/// <param name="host">The running <see cref="IHost"/>.</param>
/// <param name="token">The token to trigger shutdown.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default)
{
var applicationLifetime = host.Services.GetService<IHostApplicationLifetime>();
token.Register(state =>
{
((IHostApplicationLifetime)state).StopApplication();
},
applicationLifetime);
var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
applicationLifetime.ApplicationStopping.Register(obj =>
{
var tcs = (TaskCompletionSource<object>)obj;
tcs.TrySetResult(null);
}, waitForStop);
await waitForStop.Task;
// Host will use its default ShutdownTimeout if none is specified.
await host.StopAsync();
}
Host.cs文件代码:
internal class Host : IHost, IAsyncDisposable
{
private readonly ILogger<Host> _logger;
private readonly IHostLifetime _hostLifetime;
private readonly ApplicationLifetime _applicationLifetime;
private readonly HostOptions _options;
private IEnumerable<IHostedService> _hostedServices;
public Host(IServiceProvider services, IHostApplicationLifetime applicationLifetime, ILogger<Host> logger,
IHostLifetime hostLifetime, IOptions<HostOptions> options)
{
Services = services ?? throw new ArgumentNullException(nameof(services));
_applicationLifetime = (applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime))) as ApplicationLifetime;
if (_applicationLifetime is null)
{
throw new ArgumentException("Replacing IHostApplicationLifetime is not supported.", nameof(applicationLifetime));
}
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_hostLifetime = hostLifetime ?? throw new ArgumentNullException(nameof(hostLifetime));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public IServiceProvider Services { get; }
public async Task StartAsync(CancellationToken cancellationToken = default)
{
_logger.Starting();
using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping);
var combinedCancellationToken = combinedCancellationTokenSource.Token;
await _hostLifetime.WaitForStartAsync(combinedCancellationToken);
combinedCancellationToken.ThrowIfCancellationRequested();
_hostedServices = Services.GetService<IEnumerable<IHostedService>>(); // 获取GenericWebHostService
foreach (var hostedService in _hostedServices)
{
//
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
}
// Fire IHostApplicationLifetime.Started
_applicationLifetime.NotifyStarted();
_logger.Started();
}
public async Task StopAsync(CancellationToken cancellationToken = default)
{
_logger.Stopping();
using (var cts = new CancellationTokenSource(_options.ShutdownTimeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
{
var token = linkedCts.Token;
// Trigger IHostApplicationLifetime.ApplicationStopping
_applicationLifetime.StopApplication();
IList<Exception> exceptions = new List<Exception>();
if (_hostedServices != null) // Started?
{
foreach (var hostedService in _hostedServices.Reverse())
{
token.ThrowIfCancellationRequested();
try
{
await hostedService.StopAsync(token).ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
}
token.ThrowIfCancellationRequested();
await _hostLifetime.StopAsync(token);
// Fire IHostApplicationLifetime.Stopped
_applicationLifetime.NotifyStopped();
if (exceptions.Count > 0)
{
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
_logger.StoppedWithException(ex);
throw ex;
}
}
_logger.Stopped();
}
public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult();
public async ValueTask DisposeAsync()
{
switch (Services)
{
case IAsyncDisposable asyncDisposable:
await asyncDisposable.DisposeAsync();
break;
case IDisposable disposable:
disposable.Dispose();
break;
}
}
}
先说Kestrel,UseKestrel中间件先往容器注册,在ConfigureWebHost中往容器注册GenericWebHostService,在Host中的RunAsync从容器中获取GenericWebHostService,调用StartAsync
秋风
2020-08-24