700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍

ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍

时间:2021-11-13 15:48:34

相关推荐

ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍

在上一章 Core 运行原理解剖[1]:Hosting中,我们介绍了 Core 的启动过程,主要是对WebHost源码的探索。而本文则是对上文的一个补充,更加偏向于实战,详细的介绍一下我们在实际开发中需要对Hosting做一些配置时经常用到的几种方式。

WebHostBuild

WebHostBuild 用来构建 WebHost ,也是我们最先接触的一个类,它提供了如下方法:

ConfigureAppConfiguration

Configuration 在 Core 进行了全新的设计,使其更加灵活简洁,可以支持多种数据源。在 Core 1.x 中,我们是在Startup的构造函数中配置各种数据源的,而在 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 用来配置日志系统,在 Core 1.x 中是在Startup类的Configure方法中,通过ILoggerFactory扩展来注册的,在 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提供的扩展方法,更具体的可以看我之前介绍的 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"],用来设置 Core 停止时等待的时间。

DetailedErrors表示是否显示详细的错误信息,可为true/false1/0,默认为 false,但它没有提供直接配置的方法,可以通过UseSetting来指定IConfiguration["detailedErrors"]

ISartup

ISartup 是我们比较熟悉的,因为在我们创建一个默认的 Core 项目时,都会有一个Startup.cs文件,包含三个约定的方法,按执行顺序排列如下:

1. ConfigureServices

Core 框架本身提供了一个 DI(依赖注入)系统,并且可以非常灵活的去扩展,很容易的切换成其它的 DI 框架(如 Autofac,Ninject 等)。在 Core 中,所有的实例都是通过这个 DI 系统来获取的,并要求我们的应用程序也使用 DI 系统,以便我们能够开发出更具弹性,更易维护,测试的应用程序。总之在 Core 中,一切皆注入。关于 “依赖注入” 这里就不再多说。

在 DI 系统中,想要获取服务,首先要进行注册,而ConfigureServices方法便是用来注册服务的。

public void ConfigureServices(IServiceCollection services){services.AddScoped<IUserService, UserService>();}

如上,我们为IUserService接口注册了一个UserService类型的实例。

2. ConfigureContainer(不常用)

ConfigureContainer 是用来替换 DI 框架的,如下,我们将 Core 内置的 DI 框架替换为Autofac:

public void ConfigureContainer(ContainerBuilder builder){builder.RegisterModule(new AutofacModule());}

虽然 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 Core!");};});}

所谓中间件,也就是对HttpContext进行处理的一种便捷方式,下文会详细来介绍。而如上代码,我们注册了一个最简单的中间件,通过浏览器访问,便可以看到 “Hello Core!” 。

通常,我们的 Startup 类并没有去实现IStartup接口,这是因为我们在Configure方法中,大多时候可能需要获取一些其它的服务,如我刚才注册的IUserService,我们可以直接添加到 Configure 方法的参数列表当中:

public void Configure(IApplicationBuilder app, IUserService userService) { }

Core 会通过 DI 系统来解析到 userService 实例,但是 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, 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目录下,否则根本无法加载。不过 Core 也提供了类似插件的方式来指定IHostingStartup程序集的查找位置,可通过设置DOTNET_ADDITIONAL_DEPSASPNETCORE_HOSTINGSTARTUPASSEMBLIES来实现,而这里就不再多说。

IHostingStartup是由WebHostBuilder来调用的,执行时机较早,在创建WebHost之前执行,因此可以替换一些在 WebHost 中需要使用的服务。

IStartupFilter

IStartupFilter 是除StartupHostingStartup之处另一种配置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 Core!");};});}

最终,它他的执行顺序为:B1 -> A1 -> A2 -> B2 -> Configure 。

IHostedService

当我们希望随着 Core 的启动,来执行一些后台任务(如:定期的刷新缓存等)时,并在 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 pletedTask;}

public async Task StopAsync(CancellationToken cancellationToken) { // 发送停止信号,以通知我们的后台服务结束执行。_cts.Cancel(); // 等待后台服务的停止,而 Core 大约会等待5秒钟(可在上面介绍的UseShutdownTimeout方法中配置),如果还没有执行完会发送取消信号,以防止无限的等待下去。await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));cancellationToken.ThrowIfCancellationRequested();}}

如上,我们定义了一个在台后每5秒刷新一次缓存的服务,并在 Core 程序停止时,优雅的关闭。最后,将它注册到 DI 系统中即可:

public void ConfigureServices(IServiceCollection services){services.AddSingleton<ICacheService, CacheService>();services.AddSingleton<IHostedService, CacheHostService>();}

WebHost在启动 HTTP Server 之后,会从 DI 系统中获取所有的IHostedService,来启动我们注册的 HostedService,参见上一章。

IApplicationLifetime

IApplicationLifetime用来实现 Core 的生命周期钩子,我们可以在 Core 停止时做一些优雅的操作,如资源的清理等。它有如下定义:

public interface IApplicationLifetime{CancellationToken ApplicationStarted { get; }CancellationToken ApplicationStopping { get; }CancellationToken ApplicationStopped { get; }

void StopApplication();}

IApplicationLifetime已被 Core 注册到 DI 系统中,我们使用的时候,只需要注入即可。它有三个CancellationToken类型的属性,是异步方法终止执行的信号,表示 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 Core!");appLifetime.StopApplication();};});}

执行结果如下:

在上一章中我们提到过,IApplicationLifetime的启动信号是在WebHostStartAsync方法中触发的,而没有提到停止信号的触发,在这里补充一下:

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 的配置,结合上一章,对 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失败的问题

Core 2.0 特性介绍和使用指南

.Net Core下通过Proxy 模式 使用 WCF

.NET Core 2.0 开源Office组件 NPOI

Core Razor页面 vs MVC

Razor Page– Core 2.0新功能 Razor Page介绍

MySql 使用 EF Core 2.0 CodeFirst、DbFirst、数据库迁移(Migration)介绍及示例

.NET Core 2.0迁移技巧之web.config配置文件

core MVC 过滤器之ExceptionFilter过滤器(一)

Core 使用Cookie验证身份

Core MVC – Tag Helpers 介绍

Core MVC – Caching Tag Helpers

Core MVC – Form Tag Helpers

Core MVC – 自定义 Tag Helpers

Core MVC – Tag Helper 组件

Core 运行原理解剖[1]:Hosting

原文地址:/RainingNight/p/hosting-configure-in-asp-net-core.html

.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。