ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍
在上一章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跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Orleans解决并发之痛(二):Gra
- 下一篇: ASP.Net Core Razor 页