前一段時間,和大家分享了 ASP.NET Core技術研究-探祕Host主機啟動過程
但是沒有深入說明主機的設計。今天整理了一下主機的一些知識,結合先前的博文,完整地介紹一下.NET Core的主機的設計和構建啟動過程。
一、什麼是主機
主機是一個封裝了應用資源的物件,即:主機封裝了一堆應用資源,封裝了哪些應用資源呢?
- 依賴注入框架 DI
- Logging日誌
- Configuration 配置
- 託管服務:IHostedService服務介面的實現
二、Web主機和通用主機
先說Web主機:即ASP.NET Core Web主機,概括的講就是託管Web程式的Host。在低於 3.0 的 ASP.NET Core 版本中,Web 主機用於 HTTP 工作負載。
我們新建一個ASP.NET Core2.2的Web應用程式,在Program類的Main函式中我們可以看到整個WebHost的構造、啟動過程:
.NET Core提供Web主機的同時,還提供了一個通用主機的概念。
通用主機Host和Web主機提供了類似的架構和功能,包含依賴注入框架DI、日誌、配置、各類應用(託管服務)。通用主機的出現,給了我們更多開發的選擇,比如說後臺處理任務場景。
在.NET Core3.1版本後,微軟不再建議將 Web 主機用於 Web 應用,直接使用Host通用主機來替換WebHost,
一句話:通用主機可以託管任何型別的應用,包括 Web 應用。 通用主機將替換 Web 主機。為了向下相容,WebHost依然可以使用。
我們新建一個ASP.NET Core3.1的Web應用程式,在Program類的Main函式中我們可以看到整個WebHost的構造、啟動過程:
接下來,我們將以ASP.NET Core 3.1這個版本,介紹一下主機的構建過程和啟動過程
三、主機是如何構建的
從上述程式碼可以看到,Main函式中首先呼叫CreateHostBuilder方法,返回一個IHostBuilder。然後呼叫IHostBuilder.Build()方法完成
1. 通過Host.CreateDefaultBuilder(args): 構造IHostBuilder的預設實現HostBuilder
在CreateHostBuilder方法內部,首先呼叫了Host.CreateDefaultBuilder構造了一個HostBuilder,這個我們先看下原始碼,看看到底Host類內部做了什麼操作:
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; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); 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); // IMPORTANT: This needs to be added *before* configuration is loaded, this lets // the defaults be overridden by the configuration. if (isWindows) { // Default the EventLogLoggerProvider to warning or above logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning); } logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); logging.AddEventSourceLogger(); if (isWindows) { // Add the EventLogLoggerProvider on windows machines logging.AddEventLog(); } }) .UseDefaultServiceProvider((context, options) => { var isDevelopment = context.HostingEnvironment.IsDevelopment(); options.ValidateScopes = isDevelopment; options.ValidateOnBuild = isDevelopment; }); return builder; }
從上述.NET Core原始碼中,可以看到CreateDefaultBuilder內部構造了一個HostBuilder,同時設定了:
- 將內容根目錄(contentRootPath)設定為由 GetCurrentDirectory 返回的路徑。
- 通過以下源載入主機配置
- 環境變數(DOTNET_字首)配置
- 命令列引數配置
- 通過以下物件載入應用配置
- appsettings.json
- appsettings.{Environment}.json
- 金鑰管理器 當應用在 Development 環境中執行時
- 環境變數
- 命令列引數
- 新增日誌記錄提供程式
- 控制檯
- 除錯
- EventSource
- EventLog( Windows環境下)
- 當環境為“開發”時,啟用範圍驗證和依賴關係驗證。
以上構造完成了HostBuilder,針對ASP.NET Core應用,程式碼繼續呼叫了HostBuilder.ConfigureWebHostDefaults方法。
2. IHostBuilder.ConfigureWebHostDefaults:通過GenericWebHostBuilder對HostBuilder增加ASP.NET Core的執行時設定
構造完成HostBuilder之後,針對ASP.NET Core應用,繼續呼叫了HostBuilder.ConfigureWebHostDefaults方法。這是一個ASP.NET Core的一個擴充套件方法:
我們繼續看ConfigureWebHostDefaults擴充套件方法內部做了哪些事情:
ASP.NET Core原始碼連線:https://github.com/dotnet/aspnetcore/blob/master/src/DefaultBuilder/src/GenericHostBuilderExtensions.cs
using System; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore; namespace Microsoft.Extensions.Hosting { /// <summary> /// Extension methods for configuring the IWebHostBuilder. /// </summary> public static class GenericHostBuilderExtensions { /// <summary> /// Initializes a new instance of the <see cref="IWebHostBuilder"/> class with pre-configured defaults. /// </summary> /// <remarks> /// The following defaults are applied to the <see cref="IWebHostBuilder"/>: /// use Kestrel as the web server and configure it using the application's configuration providers, /// adds the HostFiltering middleware, /// adds the ForwardedHeaders middleware if ASPNETCORE_FORWARDEDHEADERS_ENABLED=true, /// and enable IIS integration. /// </remarks> /// <param name="builder">The <see cref="IHostBuilder" /> instance to configure</param> /// <param name="configure">The configure callback</param> /// <returns>The <see cref="IHostBuilder"/> for chaining.</returns> public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure) { return builder.ConfigureWebHost(webHostBuilder => { WebHost.ConfigureWebDefaults(webHostBuilder); configure(webHostBuilder); }); } } } © 2020 GitHub, Inc.
首先,通過類GenericHostWebHostBuilderExtensions,對IHostBuilder擴充套件一個方法:ConfigureWebHost:builder.ConfigureWebHost
在這個擴充套件方法中實現了對IWebHostBuilder的依賴注入:即將GenericWebHostBuilder例項傳入方法ConfigureWebHostDefaults內部
using System; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.Hosting { public static class GenericHostWebHostBuilderExtensions { public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure) { var webhostBuilder = new GenericWebHostBuilder(builder); configure(webhostBuilder); builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>()); return builder; } } }
通過GenericWebHostBuilder的建構函式GenericWebHostBuilder(buillder),將已有的HostBuilder增加了ASP.NET Core執行時設定。
可以參考ASP.NET Core原始碼:https://github.com/dotnet/aspnetcore/blob/release/3.1/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs
先看到這,讓我們回到ConfigureWebHostDefaults:
將上面兩段程式碼合併一下進行理解:ConfigureWebHostDefaults做了兩件事情:
①. 擴充套件IHostBuilder增加ConfigureWebHost,引入IWebHostBuilder的實現GenericWebHostBuilder,將已有的HostBuilder增加ASP.NET Core執行時的設定。
② ConfigureWebHost程式碼中的configure(webhostBuilder):對注入的IWebHostBuilder,呼叫 WebHost.ConfigureWebDefaults(webHostBuilder),啟用各類設定,如下程式碼解讀:
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) => { // Fallback services.PostConfigure<HostFilteringOptions>(options => { if (options.AllowedHosts == null || options.AllowedHosts.Count == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); // Fall back to "*" to disable. options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); // Change notification 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; // Only loopback proxies are allowed by default. Clear that restriction because forwarders are // being enabled by explicit configuration. options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>(); } services.AddRouting(); }) .UseIIS() .UseIISIntegration(); }
其內部實現了:
- 字首為 ASPNETCORE_ 的環境變數載入主機配置。
- 將 Kestrel作為預設的Web伺服器
- 新增HostFiltering中介軟體(主機篩選中介軟體)
- 如果ASPNETCORE_FORWARDEDHEADERS_ENABLED=true,新增轉接頭中介軟體ForwardedHeaders
- 啟用IIS整合
3. 返回ConfigureWebHostDefaults程式碼中的configure(webHostBuilder):執行Program類中的webBuilder.UseStartup<Startup>();
以上過程完成了IHostBuilder.ConfigureWebHostDefaults,通過GenericWebHostBuilder對HostBuilder增加ASP.NET Core的執行時設定。
接下來就是主機的Build過程了:
4. CreateHostBuilder(args).Build()
CreateHostBuilder返回的IHostBuilder,我們通過程式碼Debug,看一下具體的型別:Microsoft.Extensions.Hosting.HostBuilder。
具體的Build過程是怎麼樣的?先看下Build的原始碼:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/HostBuilder.cs
主機Build的過程主要完成了:
- BuildHostConfiguration: 構造配置系統,初始化 IConfiguration _hostConfiguration;
- CreateHostingEnvironment:構建主機HostingEnvironment環境資訊,包含ApplicationName、EnvironmentName、ContentRootPath等
- CreateHostBuilderContext:建立主機Build上下文HostBuilderContext,上下文中包含:HostingEnvironment和Configuration
- BuildAppConfiguration:構建應用程式配置
- CreateServiceProvider:建立依賴注入服務提供程式, 即依賴注入容器
四、主機是如何啟動執行的
我們先通過Debug,看一下Host的資訊:Microsoft.Extensions.Hosting.Internal.Host
這個Run方法也是一個擴充套件方法:HostingAbstractionsHostExtensions.Run
ASP.NET Core原始碼連結:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Abstractions/src/HostingAbstractionsHostExtensions.cs
其實內部轉調的還是Host.StartAsync方法,在內部啟動了DI依賴注入容器中所有註冊的服務。
.NET Core程式碼連結:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/Internal/Host.cs
五、主機中註冊一個託管服務
以一個後臺自更新(每隔5s 檢查一次程式變更、進行輸出)場景作為Demo,我們看一下如何在主機中註冊一個託管服務。
自更新服務UpdateService,需要繼承介面IHostService。
public class UpdateService : IHostedService { Task updateTask = null; CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); public Task StartAsync(CancellationToken cancellationToken) { updateTask = Task.Run(() => { while (cancellationTokenSource.Token.IsCancellationRequested==false) {
//Check new data... Console.WriteLine(DateTime.Now + ": Executed"); Task.Delay(5000).Wait(); } }); return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { cancellationTokenSource.Cancel(); return Task.CompletedTask; } }
同時,我們需要在ConfigureServices方法中,將UpdateService新增到IoC服務容器中
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IHostedService, UpdateService>(); services.AddControllers(); }
程式啟動後,可以看到以下輸出:
以上是對.NET Core主機的概念、設計初衷、構建過程、啟動執行過程、服務註冊的整理和分享。
周國慶
2020/4/18