跟我一起學.NetCore之Asp.NetCore啟動流程淺析
前言
一個Asp.NetCore專案,知道大概的啟動流程是有必要的,比如後續遇見配置資訊覆蓋等相關問題時也大概知道是什麼原因,瞭解原因之後,再去搜索引擎找答案,否則目標不明確,茫茫人海怎麼會一下找到自己想要的,除非是“偶遇”;“偶遇”太難,一起淺析一個Asp.NetCore 專案的啟動流程;
正文
先建立一個WebAPI專案,用的是.NetCore3.1,後續的專案例子都統一用.NetCore3.1,除非特殊說明;專案如下:
如上圖所示,一個WebAPI專案啟動方式本質也是一個控制檯程式,程式入口都是從Main函式開始,就從裡面方法看看大概都做了什麼,其中擇取幾個方法原始碼簡要說明主要功能,通過增加程式碼註釋的方式(我覺得這樣比較方便對應瀏覽),完整原始碼從以下兩個地址獲取,通過Everything查詢工具比較方便查詢程式碼:
主專案地址:https://github.com/dotnet/aspnetcore/tree/v3.1.0
擴充套件專案地址:https://github.com/dotnet/extensions/releases/tag/v3.1.6
1. Host.CreateDefaultBuilder方法
public static IHostBuilder CreateDefaultBuilder(string[] args) { // 例項化一個HostBuilder var builder = new HostBuilder(); // 設定根目錄builder.UseContentRoot(Directory.GetCurrentDirectory()); // 設定 Host相關配置的配置源 builder.ConfigureHostConfiguration(config => { // 從環境變數中獲取,字首名為DOTNET_ 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); // 如果是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 段相關的配置資訊新增到日誌配置中 logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); // 在控制檯輸出,所以啟動程式的時候就看見輸出日誌 logging.AddConsole(); // 在Debug視窗輸出 logging.AddDebug(); logging.AddEventSourceLogger(); // 如果是Windows系統,可以在系統日誌中輸出日誌 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; }
2. ConfigureWebHostDefaults 方法
public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure) { return builder.ConfigureWebHost(webHostBuilder => { //指定是用的伺服器及整合一些預設管道 WebHost.ConfigureWebDefaults(webHostBuilder); // 呼叫傳入的委託,這裡是外部指定Startup類做服務註冊和管道配置 configure(webHostBuilder); }); }
2.1 WebHost.ConfigureWebDefaults方法
internal static void ConfigureWebDefaults(IWebHostBuilder builder) { builder.ConfigureAppConfiguration((ctx, cb) => { if (ctx.HostingEnvironment.IsDevelopment()) { //靜態檔案環境的配置啟用 StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration); } }); // 指定Kestrel作為預設的Web伺服器 builder.UseKestrel((builderContext, options) => { options.Configure(builderContext.Configuration.GetSection("Kestrel")); })// 服務中間的註冊,包含路的中介軟體註冊 .ConfigureServices((hostingContext, services) => { // 針對配置節點AllowedHosts改變時的回撥 // 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(); })//對使用IIS相關中介軟體 .UseIIS() .UseIISIntegration(); }
3. Build方法,其實這個方法就是根據之前配置構造出一個IHost物件
public IHost Build() { if (_hostBuilt) { throw new InvalidOperationException("Build can only be called once."); } _hostBuilt = true; // 執行ConfigureHostConfiguration新增的一系列配置回撥方法 BuildHostConfiguration(); // 執行環境相關建立,如ApplicationName、EnvironmentName、ContentRootPath等 CreateHostingEnvironment(); // 構建HostBuilder CreateHostBuilderContext(); // 執行ConfigureAppConfigureation新增的一系列配置回撥方法 BuildAppConfiguration(); // 注入預設服務如:IHost、ILogging等,執行ConfigureServices新增的一系列回撥方法 CreateServiceProvider(); return _appServices.GetRequiredService<IHost>(); }
4. Run()方法,開啟伺服器,之後就可以進行請求了
綜上幾個關鍵方法,從其中Host這個關鍵詞出現很多次,其實在Asp.Net Core應用中是通過配置並啟動一個Host來完成應用程式的啟動和生命週期的管理。而Host主要就是對Web Server的配置和請求處理管理的管理,簡要流程如下圖:
在整個啟動流程中,返回的IHostBuilder中暴露配置和注入的相關介面,可用於自己定義擴充套件,接下來通過列印的方式來看看一下幾個暴露方法的執行順序,其實以上Build方法的時候已經明確了對應方法的順序;
改造程式碼如下:
Startup方法中的三個方法也增加對應的列印,執行如下:
如上圖,除了Startup中的ConfigureServices會跟隨ConfigureWebHostDefaults改變以外,其他方法順序都是固定。那這些方法主要作用都是什麼呢?如下圖:
圖中Program.ConfigureServices和Startup.ConfigureServices的執行順序會根據ConfigureWebHostDefaults的位置改變會交替變動;
總結
以上內容只是提取了其中比較關鍵的流程進行說明,並沒有詳細解析原始碼,這裡只是先淺析,後續再繼續一起深究原始碼;下一節說說依賴注入;