ASP.NET Core 运行原理解剖[1]:Hosting
SP.NET Core 是新一代的 ASP.NET,第一次出現時代號為 ASP.NET vNext,后來命名為ASP.NET 5,隨著它的完善與成熟,最終命名為 ASP.NET Core,表明它不是 ASP.NET 的升級,而是一個重新設計的Web開發框架。而它一個非常重要的變化就是它不再依賴于IIS,而是一個獨立的自寄宿的控制臺應用程序,這也是它可以跨平臺的基石,而本文就來詳細探討一下 ASP.NET Core 的啟動過程。
目錄
本系列文章將會從源碼分析來講解 ASP.NET Core 的運行原理,分為以下幾個章節:
ASP.NET Core 運行原理解剖[1]:Hosting(Current)
ASP.NET Core 運行原理解剖[2]:Hosting補充之配置介紹
ASP.NET Core 運行原理解剖[3]:Middleware-請求管道的構成
ASP.NET Core 運行原理解剖[4]:進入HttpContext的世界(待續)
ASP.NET Core 運行原理解剖[5]:Authentication(待續)
前言
我們先回顧一下以前的 ASP.NET 是怎么來運行的:
ASP.NET 是嚴重依賴于IIS的,System.Web?中有很多方法都是直接調用的 IIS API,并且它還是駐留在IIS進程中的。而 ASP.NET Core 的運行則是一個完全獨立的控制臺程序,它有自己的?Kestrel?Server,可以直接對外部提供服務。
不過?Kestrel?的功能相對較于簡單,所以我們還是需要一個反向代理服務器將?Kestrel?服務器保護起來。而微軟也為我們提供了?UseIISIntegration?方法,方便與IIS進行集成。因此,在 Windows 下,通常還是使用IIS來部署,那么,此時與 ASP.NET 的運行方式又有什么區別呢?
通過上圖,可以很清楚的明白它們的區別。在 ASP.NET Core 中,IIS 是通過 HTTP 的方式來調用我們的 ASP.NET Core 程序。而部署在IIS中時,并不需要我們手動來啟動 ASP.NET Core 的控制臺程序,這是因為IIS新增了一個?AspNetCoreModule?模塊,它負責 ASP.NET Core 程序的啟動與停止,并能監聽 ASP.NET Core 程序的狀態,在我們的應用程序意外崩潰時重新啟動。
下面開始進入正題,進入到 ASP.NET Core 的代碼中去。
WebHost的創建
對于一個程序控制臺程序來說,它的入口點便是?Program?中的?Main?方法,ASP.NET Core 程序自然也不例外:
public class Program{ ??public static void Main(string[] args) ? ?{BuildWebHost(args).Run();} ? ?
?
?public static IWebHost BuildWebHost(string[] args) =>WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build(); }
WebHost.CreateDefaultBuilder?是在 2.0 中新增的,在?MetaPackages?程序集中,代碼如下:
public static IWebHostBuilder CreateDefaultBuilder(string[] args){ ??var builder = new WebHostBuilder().UseKestrel().UseContentRoot(Directory.GetCurrentDirectory()).ConfigureAppConfiguration((hostingContext, config) =>{ ? ? ? ?
?? ?var env = hostingContext.HostingEnvironment; ? ? ? ?
?? ?? ?config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
?? ??.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); ? ? ? ?
?? ?? ? ?if (env.IsDevelopment()){ ? ? ? ? ? ?
?? ?? ? ?? ?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) =>{logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));logging.AddConsole();logging.AddDebug();}).UseIISIntegration().UseDefaultServiceProvider((context, options) =>{options.ValidateScopes = context.HostingEnvironment.IsDevelopment();}); ? ?return builder; }
它只是用來簡化我們的代碼,做了一些基本的配置:
注冊?Kestrel?中間件,指定 WebHost 要使用的 Server(HTTP服務器)。
設置?Content?根目錄,將當前項目的根目錄作為 ContentRoot 的目錄。
讀取?appsettinggs.json?配置文件,開發環境下的?UserSecrets?以及環境變量和命令行參數。
讀取配置文件中的?Logging?節點,對日志系統進行配置。
添加?IISIntegration?中間件。
設置開發環境下,?ServiceProvider?的?ValidateScopes?為?true,避免直接在?Configure?方法中獲取?Scope?實例。
然后指定?Startup?類,最后通過?Build?方法創建?WebHost?對象,而WebHostBuilder?的代碼較多,感興趣的可以去看完整代碼:?WebHostBuilder,而在這里我只展示部分代碼片段來幫助理解:
public IWebHost Build(){ ? ?? ? ? ?var hostingServices = BuildCommonServices(out var hostingStartupErrors); ?
? ?var applicationServices = hostingServices.Clone(); ?
? ?var hostingServiceProvider = hostingServices.BuildServiceProvider();AddApplicationServices(applicationServices, hostingServiceProvider); }
Build?中的?BuildCommonServices?方法主要有兩個功能:
首先在程序集中查找?HostingStartupAttribute:
if (!_options.PreventHostingStartup) { ? ?var exceptions = new List<Exception>(); ? ?// Execute the hosting startup assembliesforeach (var assemblyName in _options.HostingStartupAssemblies){ ? ? ?? ? ? ?var assembly = Assembly.Load(new AssemblyName(assemblyName)); ? ? ? ? ? ? ? foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>()){ ? ? ? ? ?
? ? ? ? ? ?var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);hostingStartup.Configure(this);}} }
HostingStartupAttribute?給我們一個在其它程序集中做一些啟動配置的機會,在我們進行多層開發及模塊化的時候非常有用,下一站會詳細解釋。
然后便是查找我們的?Startup?類:
if (!string.IsNullOrEmpty(_options.StartupAssembly)) { ?? ??var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);
? ??if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())){services.AddSingleton(typeof(IStartup), startupType);} ? ?
? ?else{services.AddSingleton(typeof(IStartup), sp =>{ ? ? ?
? ? ? ? ? ?var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); ? ?
? ?? ? ? ?var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName); ? ?
? ?? ? ? ?return new ConventionBasedStartup(methods);});} }
首先是判斷是否有?_options.StartupAssembly,對應配置文件中的 "startupAssembly" ,如果我們沒有設置,那便是空的,并不會執行上面代碼。通常我們會使用?UseStartup<Startup>?的方法來注冊?Startup?類,而他們的作用是一樣的,都是將我們的?Startup?類做為一個單例注冊到了 DI 系統。
而最終?BuildCommonServices?返回一個?IServiceCollection,用于構建?hostingServiceProvider:
var hostingServices = BuildCommonServices(out var hostingStartupErrors);var applicationServices = hostingServices.Clone();
var hostingServiceProvider = hostingServices.BuildServiceProvider();
接下來創建?WebHost?:
public IWebHost Build(){ ? ?? ? ?var host = new WebHost(applicationServices,hostingServiceProvider,_options,_config,hostingStartupErrors);}host.Initialize(); ?
? ?return host; }
這里需要說明的,hostingServiceProvider?是 ASP.NET Core 中的第一個?ServiceProvider,也是根?ServiceProvider,但它是在我們的?Starpup?類執行之前創建的,也就是說并不會包含我們在?ConfigureServices?中注冊的服務(但包含使用?HostingStartupAttribute?注冊的服務)。
WebHost啟動流程
在上一步,創建完?WebHost?之后,便調用它的?Run?方法,而?Run?方法再去調用?WebHost?的?StartAsync?方法,開始 ASP.NET Core 的啟動工作,主要包含以下幾個步驟:
1. 初始化,構建 RequestDelegate
RequestDelegate?是我們的應用程序處理請求,輸出響應的整個過程,也就是我們的 ASP.NET Core 請求管道。
而它有如下定義:
public delegate Task RequestDelegate(HttpContext context);這里不再對?RequestDelegate?進行過多的介紹,以后會詳細解釋。
1.1. 調用 Startup 中的?ConfigureServices?方法
在前面介紹過,我們的 Startup 類已經注冊到了 ASP.NET Coer 的 DI 系統中,因此可以直接從 DI 中獲取:
private IStartup _startup;private IServiceProvider _applicationServices;_startup = _hostingServiceProvider.GetRequiredService<IStartup>();_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
這里使用的?_hostingServiceProvider?是我們在 WebHost 中創建的根?ServieProvider。
1.2. 初始化 Http Server
Server 是一個HTTP服務器,負責HTTP的監聽,接收一組?FeatureCollection?類型的原始請求,并將其包裝成?HttpContext?以供我們的應用程序完成響應的處理。
public interface IServer : IDisposable{IFeatureCollection Features { get; }Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken); ?? ?Task StopAsync(CancellationToken cancellationToken); }
而上面注冊的?Kestrel?便是默認的?Server?:
public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder){hostBuilder.UseLibuv(); ?? ?return hostBuilder.ConfigureServices(services =>{services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();services.AddSingleton<IServer, KestrelServer>();}); }
Server的初始化主要是配置要監聽的地址:
private void EnsureServer(){ ? ?? ?if (Server == null){Server = _applicationServices.GetRequiredService<IServer>(); ? ? ? ?var serverAddressesFeature = Server.Features?.Get<IServerAddressesFeature>(); ? ? ?
? ? ? ?var addresses = serverAddressesFeature?.Addresses; ? ?
? ?? ?if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0){ ? ? ? ?
? ?? ? ? ? ?var urls = _config[WebHostDefaults.ServerUrlsKey] ?? _config[DeprecatedServerUrlsKey]; ? ? ? ?
? ?? ? ?? ?if (!string.IsNullOrEmpty(urls)){serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(_config, WebHostDefaults.PreferHostingUrlsKey); ? ? ? ? ? ? ? ?foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)){addresses.Add(value);}}}} }
Addresses 默認是通過在?launchSettings.json?中來查找的。
1.3. 創建 IApplicationBuilder
IApplicationBuilder 用于構建應用程序的請求管道,也就是生成?RequestDelegate,有如下定義:
public interface IApplicationBuilder{IServiceProvider ApplicationServices { get; set; }IFeatureCollection ServerFeatures { get; }IDictionary<string, object> Properties { get; } ?? ??RequestDelegate Build(); ? ?IApplicationBuilder New(); ? ?IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); }
而它的創建過程是通過?ApplicationBuilderFactory?來創建的:
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();? ?var builder = builderFactory.CreateBuilder(Server.Features); builder.ApplicationServices = _applicationServices;
IApplicationBuilderFactory 的默認實現 ApplicationBuilderFactory:
public IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures){ ? ?return new ApplicationBuilder(_serviceProvider, serverFeatures); }ApplicationBuilder 的實現方式就不在這里多說了,在講中間件的時候再來細說。
1.4. 配置 IApplicationBuilder
我們比較的熟悉的是在?Startup?類的?Configure?方法中對?IApplicationBuilder?進行配置,其實還有一個?IStartupFilter?也可以用來配置?IApplicationBuilder,并且在?Startup?類的Configure?方法之前執行:
var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>(); Action<IApplicationBuilder> configure = _startup.Configure;foreach (var filter in startupFilters.Reverse()) {configure = filter.Configure(configure); }configure(builder);
然后調用?IApplicationBuilder?的?Build?方法,便完成了?RequestDelegate?的創建:
private RequestDelegate BuildApplication(){... ? ?return builder.Build(); }2. 啟動 Server,監聽請求并響應
Server 本身是并不清楚 HttpContext 的細節的,因此它需要接收一個?IHttpApplication 類型的參數,來負責 HttpContext 的創建,由如下定義:
public interface IHttpApplication<TContext> { ??TContext CreateContext(IFeatureCollection contextFeatures); ?
?Task ProcessRequestAsync(TContext context); ? ?
void DisposeContext(TContext context, Exception exception); }
它的默認實現是?HostingApplication?類,而?ProcessRequestAsync?方法則調用我們上面創建的?RequestDelegate?委托,來完成對?HttpContext?的處理:
public class HostingApplication : IHttpApplication<HostingApplication.Context> { ??private readonly RequestDelegate _application; ?
?public Task ProcessRequestAsync(Context context) ? ?{ ? ?
?? ?return _application(context.HttpContext);} }
最后啟動 Server:
var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();var hostingApp = new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory);
await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
Server 會綁定一個監聽端口,注冊HTTP連接事件,最終交給?Http2Stream<TContext>?來處理,通過上面的?hostingApp?來切入到我們的應用程序中,完成整個請求的處理:
public class Http2Stream<TContext> : Http2Stream{ ?? ? ?private readonly IHttpApplication<TContext> _application; ?
? ? ?public override async Task ProcessRequestAsync() ? ?{... ? ? ?
?? ? ? ?var context = _application.CreateContext(this); ? ?
?? ? ? ?try{ ? ? ? ? ?
?? ? ? ? ? ?await _application.ProcessRequestAsync(context);...} ? ? ?
?? ? ? ?finally{_application.DisposeContext(context, _applicationException);...}...} }
3. 啟動 HostedService
HostedService?為我們提供一個注冊后臺運行服務的機會,它會在隨著我們的 ASP.NET Core 程序啟動而啟動,并在 ASP.NET Core 停止時進行優雅的關閉,有如下定義:
public interface IHostedService{ ???Task StartAsync(CancellationToken cancellationToken);
??Task StopAsync(CancellationToken cancellationToken); }
而它是通過?HostedServiceExecutor?來執行的:
public class HostedServiceExecutor{ ???private readonly IEnumerable<IHostedService> _services; ?
??public async Task StartAsync(CancellationToken token) ? ?{ ?
?? ? ? ?await ExecuteAsync(service => service.StartAsync(token));} ? ?
?? ? ? ?
??public async Task StopAsync(CancellationToken token) ? ?{ ?
?? ? ? ?await ExecuteAsync(service => service.StopAsync(token));} ?
?? ? ? ?
?private async Task ExecuteAsync(Func<IHostedService, Task> callback) ?
?{ ? ? ? ?foreach (var service in _services){ ? ? ? ? ?
? ? ? ? ??await callback(service);}} }
WebHost 會調用?HostedServiceExecutor?的?StartAsync?,從而完成對 HostedService 的啟動:
_applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime; _hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();// Fire IApplicationLifetime.Started_applicationLifetime?.NotifyStarted();// Fire IHostedService.Start
await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
這里還有對?IApplicationLifetime?啟動事件的觸發,以后會介紹一下?IApplicationLifetime?的用途。
到此?WebHost?的整個啟動過程介紹完畢。
總結
本文粗略地介紹了一下 ASP.NET Core 中?WebHost?創建及啟動,它也是 ASP.NET Core 中的宿主,包含 HttpServer 的啟動與監聽,而其中也涉及到了很多關鍵點,對我們以后的開發非常有用,由于篇幅有限,下一章再來介紹一些本文沒有解釋清楚的概念。
參考文章:
Publishing-and-Running-ASPNET-Core-Applications-with-IIS
相關文章:?
.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 組件
原文地址:http://www.cnblogs.com/RainingNight/p/hosting-in-asp-net-core.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的ASP.NET Core 运行原理解剖[1]:Hosting的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Orleans解决并发之痛(一):单线程
- 下一篇: 2017(深圳) .NET技术分享交流会