在上一章ASP.NET Core 运行原理解剖[1]:Hosting中,我们介绍了 ASP.NET Core 的启动过程,主要是对 WebHost 源码的探索。而本文则是对上文的一个补充,更加偏向于实战,详细的介绍一下我们在实际开发中需要对 Hosting 做一些配置时经常用到的几种方式。
WebHostBuild
WebHostBuild 用来构建 WebHost ,也是我们最先接触的一个类,它提供了如下方法:
ConfigureAppConfiguration
Configuration 在 ASP.NET Core 进行了全新的设计,使其更加灵活简洁,可以支持多种数据源。在 ASP.NET Core 1.x 中,我们是在Startup
的构造函数中配置各种数据源的,而在 ASP.NET Core 2.0 中则移动了到Program
中,这样能与控制台应用程序保持一致:
public static class WebHostBuilderExtensions{
public static IWebHostBuilder ConfigureAppConfiguration(this IWebHostBuilder hostBuilder, Action<IConfigurationBuilder> configureDelegate)
{
return hostBuilder.ConfigureAppConfiguration((context, builder) => configureDelegate(builder));}
}
public class WebHostBuilder : IWebHostBuilder{
private List<Action<WebHostBuilderContext, IConfigurationBuilder>> _configureAppConfigurationBuilderDelegates;
public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate) {
if (configureDelegate == null){
throw new ArgumentNullException(nameof(configureDelegate));}_configureAppConfigurationBuilderDelegates.Add(configureDelegate); return this;}
}
而_configureAppConfigurationBuilderDelegates
委托会在 WebHostBuilder 的Build
方法中执行,生成 IConfiguration 对象并以单例的形式注册到 DI 系统中, 我们可以在Startup
以及应用程序的任何地方,通过 DI 系统来获取到。
而在 上一章 中也介绍过,在CreateDefaultBuilder
中会通过该方法来添加appsettinggs.json
等基本配置的配置源。
UseSetting
UseSetting 是一个非常重要的方法,它用来配置 WebHost 中的 IConfiguration 对象。需要注意与上面ConfigureAppConfiguration
的区别, WebHost 中的 Configuration 只限于在 WebHost 使用,并且我们不能配置它的数据源,它只会读取ASPNETCORE_
开头的环境变量:
private IConfiguration _config;
public WebHostBuilder(){_config = new ConfigurationBuilder().AddEnvironmentVariables(prefix: "ASPNETCORE_").Build();
}
而我们比较熟悉的当前执行环境,也是通过该_config
来读取的,虽然我们不能配置它的数据源,但是它为我们提供了一个UseSetting
方法,为我们提供了一个设置_config
的机会:
public string GetSetting(string key){
return _config[key];
}
而我们通过UseSetting
设置的变量最终也会以MemoryConfigurationProvider
的形式添加到上面介绍的ConfigureAppConfiguration
所配置的IConfiguration
对象中。
UseStartup
UseStartup 这个我们都比较熟悉,它用来显式注册我们的Startup
类,可以使用泛性,Type , 和程序集名称三种方式来注册:
// 常用的方法
public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class{
return hostBuilder.UseStartup(typeof(TStartup));
}
// 通过指定的程序集来注册 Startup 类
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, string startupAssemblyName){
if (startupAssemblyName == null){
throw new ArgumentNullException(nameof(startupAssemblyName));}
return hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName).UseSetting(WebHostDefaults.StartupAssemblyKey, startupAssemblyName);
}
// 最终的 Startup 类注册方法,上面两种只是一种简写形式
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType){....
}
具体的注册方式,在 上一章 也介绍过,就是通过反射创建实例,然后注入到 DI 系统中。
ConfigureLogging
ConfigureLogging 用来配置日志系统,在 ASP.NET Core 1.x 中是在Startup
类的Configure
方法中,通过ILoggerFactory
扩展来注册的,在 ASP.NET Core 中也变得更加简洁,并且统一通过 WebHostBuild 来配置:
public static class WebHostBuilderExtensions{
public static IWebHostBuilder ConfigureLogging(this IWebHostBuilder hostBuilder, Action<ILoggingBuilder> configureLogging) {
return hostBuilder.ConfigureServices(collection => collection.AddLogging(configureLogging));}
public static IWebHostBuilder ConfigureLogging(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, ILoggingBuilder> configureLogging) {
return hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(context, builder)));}
}
AddLogging 是Microsoft.Extensions.Logging
提供的扩展方法,更具体的可以看我之前介绍的 ASP.NET Core 源码学习之 Logging 系列。
ConfigureServices
在上面的几个方法中,多次用到 ConfigureServices,而 ConfigureServices 与 Starup 中的 ConfigureServices 类似,都是用来注册服务的:
private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates;
public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices){
if (configureServices == null){
throw new ArgumentNullException(nameof(configureServices));}_configureServicesDelegates.Add(configureServices);
return this;
}
但不同的是_configureServicesDelegates
的执行时机较早,是在WebHostBuilder的Build
方法中执行的,所以会参与 WebHost 中hostingServiceProvider
的构建。
其它
WebHostBuild 中还有很多配置的方法,就不再一一细说,在这里简单介绍一下:
UseContentRoot 使用
UseSetting
方法配置IConfiguration["contentRoot"]
,表示应用程序所在的默认文件夹地址,如 MVC 中视图的查询根目录。UseWebRoot 使用
UseSetting
方法配置IConfiguration["webroot"]
,用来指定可让外部可访问的静态资源路径,默认为wwwroot
,并且是以contentRoot
为根目录。CaptureStartupErrors 使用
UseSetting
方法配置IConfiguration["captureStartupErrors"]
,表示是否捕捉启动时的异常,如果为ture
,则在启动时发生异常也会启动 Http Server,并显示错误页面,否则,不会启动 Http Server。UseEnvironment 使用
UseSetting
方法配置IConfiguration["environment"]
,用来指定执行环境。UseServer 用来配置 Http Server 服务,
UseKestrel
便是此方法的简写形式。UseUrls 使用
UseSetting
方法配置IConfiguration["urls"]
,用来配置 Http 服务器地址,多个使用;
分割。UseShutdownTimeout 使用
UseSetting
方法配置IConfiguration["shutdownTimeoutSeconds"]
,用来设置 ASP.NET Core 停止时等待的时间。DetailedErrors 表示是否显示详细的错误信息,可为
true/false
或1/0
,默认为 false,但它没有提供直接配置的方法,可以通过UseSetting
来指定IConfiguration["detailedErrors"]
。
ISartup
ISartup 是我们比较熟悉的,因为在我们创建一个默认的 ASP.NET Core 项目时,都会有一个Startup.cs
文件,包含三个约定的方法,按执行顺序排列如下:
1. ConfigureServices
ASP.NET Core 框架本身提供了一个 DI(依赖注入)系统,并且可以非常灵活的去扩展,很容易的切换成其它的 DI 框架(如 Autofac,Ninject 等)。在 ASP.NET Core 中,所有的实例都是通过这个 DI 系统来获取的,并要求我们的应用程序也使用 DI 系统,以便我们能够开发出更具弹性,更易维护,测试的应用程序。总之在 ASP.NET Core 中,一切皆注入。关于 “依赖注入” 这里就不再多说。
在 DI 系统中,想要获取服务,首先要进行注册,而ConfigureServices
方法便是用来注册服务的。
public void ConfigureServices(IServiceCollection services){services.AddScoped<IUserService, UserService>();
}
如上,我们为IUserService
接口注册了一个UserService
类型的实例。
2. ConfigureContainer(不常用)
ConfigureContainer 是用来替换 DI 框架的,如下,我们将 ASP.NET Core 内置的 DI 框架替换为 Autofac :
public void ConfigureContainer(ContainerBuilder builder){builder.RegisterModule(new AutofacModule());
}
虽然 ASP.NET Core 自带的 DI 系统只提供了构造函数注入,以及不支持命名实例等,但我喜欢它的简洁,并且不太喜欢依赖太多第三库,一直也只使用了内置的DI框架,因此对这个方法也不太了解,就不再多说。
3. Configure
Configure 接收一个IApplicationBuilder
类型参数,而IApplicationBuilder
在 上一章 中介绍过,它是用来构建请求管道的,因此,也可以说 Configure 方法是用来配置请求管道的,通常会在这里会注册一些中间件。
public void Configure(IApplicationBuilder app){app.Use(next =>{ return async (context) =>{ await context.Response.WriteAsync("Hello ASP.NET Core!");};});
}
所谓中间件,也就是对 HttpContext 进行处理的一种便捷方式,下文会详细来介绍。而如上代码,我们注册了一个最简单的中间件,通过浏览器访问,便可以看到 “Hello ASP.NET Core!” 。
通常,我们的 Startup 类并没有去实现IStartup
接口,这是因为我们在Configure
方法中,大多时候可能需要获取一些其它的服务,如我刚才注册的IUserService
,我们可以直接添加到 Configure 方法的参数列表当中:
public void Configure(IApplicationBuilder app, IUserService userService) { }
ASP.NET Core 会通过 DI 系统来解析到 userService 实例,但是 ASP.NET Core 中的 DI 系统是不支持普通方法的参数注入的,而是手动通过反射的方式来实现的:
services.AddSingleton(typeof(IStartup), sp =>
{
var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
return new ConventionBasedStartup(methods);
});
而通过反射也可以为我们带来更大的灵活性,上面的LoadMethods
方法会根据当前的执行环境名称来查找适当的方法名:
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName){
var configureMethod = FindConfigureDelegate(startupType, environmentName);
}
private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName){
var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
return new ConfigureBuilder(configureMethod);
}
更具体的可以查看 StartupLoader,ASP.NET Core 会根据当前环境的不同,而执行不同的方法:
public void ConfigureServices(IServiceCollection services) { }
public void ConfigureDevelopmentServices(IServiceCollection services) { }
public void ConfigureContainer(ContainerBuilder builder) {}
public void ConfigureDevelopmentContainer(ContainerBuilder builder) { }
public void Configure(IApplicationBuilder app) { }
public void ConfigureDevelopment(IApplicationBuilder app) { }
如上,当在Development
环境上执行时,会选择带Development
的方法来执行。
而在默认模版中是通过UseStartup<Startup>
的方式来注册 Startup 类的,我们也可以使用上面介绍的指定程序集名称的方式来注册:
public static IWebHost BuildWebHost(string[] args) =>WebHost.CreateDefaultBuilder(args).UseStartup("EmptyWebDemo").Build();
如上,我们指定在 EmptyWebDemo 中查找Startup
类,这样还有一个额外的好处,WebHost 同样会根据当前的执行环境来选择不同的Startup
类(如StartupDevelopment
),与上面介绍的Startup中方法的查询方式一样。
IHostingStartup
上面,我们介绍了Sartup
,而一个项目中只能一个Sartup
,因为如果配置多个,则最后一个会覆盖之前的。而在一个多层项目中,Sartup类一般是放在展现层中,我们在其它层也需要注册一些服务或者配置请求管道时,通常会写一个扩展方法:
public static class EfRepositoryExtensions{
public static void AddEF(this IServiceCollection services,string connectionStringName) { services.AddDbContext<AppDbContext>(options =>options.UseSqlServer(connectionStringName), opt => opt.EnableRetryOnFailure()));services.TryAddScoped<IDbContext, AppDbContext>();services.TryAddScoped(typeof(IRepository<,>), typeof(EfRepository<,>));...}
public static void UseEF(IApplicationBuilder app) {app.UseIdentity();}
}
然后在 Startup 中调用这些扩展方法:
public void ConfigureDevelopmentServices(IServiceCollection services){services.AddEF(Configuration.GetConnectionString("DefaultConnection");
}
public void ConfigureDevelopment(IApplicationBuilder app){services.UseEF();
}
感觉这种方式非常丑陋,而在上一章中,我们知道 WebHost 会在 Starup 这前调用 IHostingStartup,于是我们便以如下方式来实现:
[assembly: HostingStartup(typeof(Zero.EntityFramework.EFRepositoryStartup))]
namespace Zero.EntityFramework{
public class EFRepositoryStartup : IHostingStartup{
public void Configure(IWebHostBuilder builder) {builder.ConfigureServices(services =>{services.AddDbContext<AppDbContext>(options =>options.UseSqlServer(connectionStringName), opt => opt.EnableRetryOnFailure()));services.TryAddScoped<IDbContext, AppDbContext>();services.TryAddScoped(typeof(IRepository<,>), typeof(EfRepository<,>));...}); builder.Configure(app => {app.UseIdentity();});}}
}
如上,只需实现 IHostingStartup 接口,要清爽简单的多,怎一个爽字了得!不过,还需要进行注册才会被WebHost执行,首先要指定HostingStartupAttribute
程序集特性,其次需要配置 WebHost 中的 IConfiguration[hostingStartupAssemblies]
,以便 WebHost 能找到我们的程序集,可以使用如下方式配置:
WebHost.CreateDefaultBuilder(args) // 如需指定多个程序集时,使用 ; 分割.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "Zero.Application;Zero.EntityFramework")
这样便完成了 IHostingStartup 注册,不过还需要将包含IHostingStartup
的程序集放到 Bin 目录下,否则根本无法加载。不过 ASP.NET Core 也提供了类似插件的方式来指定IHostingStartup
程序集的查找位置,可通过设置DOTNET_ADDITIONAL_DEPS
和ASPNETCORE_HOSTINGSTARTUPASSEMBLIES
来实现,而这里就不再多说。
IHostingStartup 是由 WebHostBuilder 来调用的,执行时机较早,在创建 WebHost 之前执行,因此可以替换一些在 WebHost 中需要使用的服务。
IStartupFilter
IStartupFilter 是除Startup
和HostingStartup
之处另一种配置IApplicationBuilder
的方式:
public interface IStartupFilter{
Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
}
它只有一个Configure
方法,是对 Starup 类中Configure
方法的拦截器,给我们一个在Configure
方法执行之前进行一些配置的机会。
让我们实践一把,先定义2个 StartupFilter:
public class A : IStartupFilter{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) {Console.WriteLine("This is A1!");
return app =>{Console.WriteLine("This is A2!");next(app);};}
}
public class B : IStartupFilter{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) {Console.WriteLine("This is B1!");
return app =>{Console.WriteLine("This is B2!");next(app);};}
}
然后让他们注册到DI系统中,WebHost 在执行 Starup 类中Configure
方法之前,会从 DI 系统中获取所有的IStartupFilter
来执行:
public void ConfigureServices(IServiceCollection services){services.AddSingleton<IStartupFilter, A>();services.AddSingleton<IStartupFilter, B>();
}public void Configure(IApplicationBuilder app){Console.WriteLine("This is Configure!");app.Use(next =>{ return async (context) =>{ await context.Response.WriteAsync("Hello ASP.NET Core!");};});
}
最终,它他的执行顺序为:B1 -> A1 -> A2 -> B2 -> Configure 。
IHostedService
当我们希望随着 ASP.NET Core 的启动,来执行一些后台任务(如:定期的刷新缓存等)时,并在 ASP.NET Core 停止时,可以优雅的关闭,则可以使用IHostedService
,它有如下定义:
public interface IHostedService{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
很简单,只有开始和停止两个方法,它的用法大概是这个样子的:
public class CacheHostService : IHostedService{
private readonly ICacheService _cacheService;
private CancellationTokenSource _cts;
private Task _executingTask;
public CacheHostService(ICacheService cacheService) {_cacheService = cacheService;}
public Task StartAsync(CancellationToken cancellationToken) {_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);_executingTask = Task.Run(async () =>{ while (!_cts.IsCancellationRequested){Console.WriteLine("cancellationToken:" + _cts.IsCancellationRequested);
await _cacheService.Refresh();
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);}});
return Task.CompletedTask;}
public async Task StopAsync(CancellationToken cancellationToken) { // 发送停止信号,以通知我们的后台服务结束执行。_cts.Cancel(); // 等待后台服务的停止,而 ASP.NET Core 大约会等待5秒钟(可在上面介绍的UseShutdownTimeout方法中配置),如果还没有执行完会发送取消信号,以防止无限的等待下去。await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));cancellationToken.ThrowIfCancellationRequested();}
}
如上,我们定义了一个在台后每5秒刷新一次缓存的服务,并在 ASP.NET Core 程序停止时,优雅的关闭。最后,将它注册到 DI 系统中即可:
public void ConfigureServices(IServiceCollection services){services.AddSingleton<ICacheService, CacheService>();services.AddSingleton<IHostedService, CacheHostService>();
}
WebHost 在启动 HTTP Server 之后,会从 DI 系统中获取所有的IHostedService
,来启动我们注册的 HostedService,参见上一章 。
IApplicationLifetime
IApplicationLifetime
用来实现 ASP.NET Core 的生命周期钩子,我们可以在 ASP.NET Core 停止时做一些优雅的操作,如资源的清理等。它有如下定义:
public interface IApplicationLifetime{CancellationToken ApplicationStarted { get; }CancellationToken ApplicationStopping { get; }CancellationToken ApplicationStopped { get; }
void StopApplication();
}
IApplicationLifetime
已被 ASP.NET Core 注册到 DI 系统中,我们使用的时候,只需要注入即可。它有三个CancellationToken
类型的属性,是异步方法终止执行的信号,表示 ASP.NET Core 生命周期的三个阶段:启动,开始停止,已停止。
public void Configure(IApplicationBuilder app, IApplicationLifetime appLifetime){appLifetime.ApplicationStarted.Register(() => Console.WriteLine("Started"));appLifetime.ApplicationStopping.Register(() => Console.WriteLine("Stopping"));appLifetime.ApplicationStopped.Register(() =>{Console.WriteLine("Stopped");Console.ReadKey();});app.Use(next =>{ return async (context) =>{ await context.Response.WriteAsync("Hello ASP.NET Core!");appLifetime.StopApplication();};});
}
执行结果如下:
在上一章中我们提到过, IApplicationLifetime
的启动信号是在 WebHost 的StartAsync
方法中触发的,而没有提到停止信号的触发,在这里补充一下:
internal class WebHost : IWebHost{
public async Task StopAsync(CancellationToken cancellationToken = default(CancellationToken)) {.... // 设置 Task 的超时时间,上文在 IHostedService 中提到过var timeoutToken = new CancellationTokenSource(Options.ShutdownTimeout).Token; if (!cancellationToken.CanBeCanceled){cancellationToken = timeoutToken;} else{cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken).Token;} // 触发 Stopping 信号_applicationLifetime?.StopApplication(); // 停止 Http Serverif (Server != null){ await Server.StopAsync(cancellationToken).ConfigureAwait(false);} // 停止 我们注册的 IHostServiceif (_hostedServiceExecutor != null){ await _hostedServiceExecutor.StopAsync(cancellationToken).ConfigureAwait(false);} // 发送 Stopped 通知_applicationLifetime?.NotifyStopped();}
}
总结
本文详细介绍了对 WebHost 的配置,结合 上一章,对 ASP.NET Core 的启动流程也基本清楚了,下一章就来介绍一下请求管道的创建,敬请期待!
参考资料:
ASP-NET-Core-2-IHostedService
ASPNET-Core-2.0-Stripping-Away-Cross-Cutting-Concerns
Looking-at-asp-net-cores-iapplicationlifetime
相关文章:
.NET Core 2.0 正式发布信息汇总
.NET Standard 2.0 特性介绍和使用指南
.NET Core 2.0 的dll实时更新、https、依赖包变更问题及解决
.NET Core 2.0 特性介绍和使用指南
Entity Framework Core 2.0 新特性
体验 PHP under .NET Core
.NET Core 2.0使用NLog
升级项目到.NET Core 2.0,在Linux上安装Docker,并成功部署
解决Visual Studio For Mac Restore失败的问题
ASP.NET Core 2.0 特性介绍和使用指南
.Net Core下通过Proxy 模式 使用 WCF
.NET Core 2.0 开源Office组件 NPOI
ASP.NET Core Razor页面 vs MVC
Razor Page–Asp.Net Core 2.0新功能 Razor Page介绍
MySql 使用 EF Core 2.0 CodeFirst、DbFirst、数据库迁移(Migration)介绍及示例
.NET Core 2.0迁移技巧之web.config配置文件
asp.net core MVC 过滤器之ExceptionFilter过滤器(一)
ASP.NET Core 使用Cookie验证身份
ASP.NET Core MVC – Tag Helpers 介绍
ASP.NET Core MVC – Caching Tag Helpers
ASP.NET Core MVC – Form Tag Helpers
ASP.NET Core MVC – 自定义 Tag Helpers
ASP.NET Core MVC – Tag Helper 组件
ASP.NET Core 运行原理解剖[1]:Hosting
原文地址:http://www.cnblogs.com/RainingNight/p/hosting-configure-in-asp-net-core.html
.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注