起因
昨天修改博客程序,发现有部分代码是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代码:
namespace Microsoft.Extensions.Hosting
{
public static class Host
{
public static IHostBuilder CreateDefaultBuilder() =>
CreateDefaultBuilder(args: null);
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) =>
{
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((hostingContext, logging) =>
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
}
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
if (isWindows)
{
logging.AddEventLog();
}
})
.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);
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);
}
});
builder.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"));
})
.ConfigureServices((hostingContext, services) =>
{
services.PostConfigure<HostFilteringOptions>(options =>
{
if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
{
var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
}
});
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;
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();
return _appServices.GetRequiredService<IHost>();
}
private void BuildHostConfiguration()
{
var configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection();
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))
{
_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()
{
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);
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 (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.");
}
_ = _appServices.GetService<IConfiguration>();
}
最后看一下Run内部都有什么黑科技.
Run函数在HostingAbstractionsHostExtensions.cs文件中,可以看到又是通过扩展方法往Host对象添加函数.
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);
await host.WaitForShutdownAsync(token);
}
finally
{
if (host is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
host.Dispose();
}
}
}
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;
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);
}
_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;
_applicationLifetime.StopApplication();
IList<Exception> exceptions = new List<Exception>();
if (_hostedServices != null)
{
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);
_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