跟我一起學.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的位置改變會交替變動;

 

總結

以上內容只是提取了其中比較關鍵的流程進行說明,並沒有詳細解析源代碼,這里只是先淺析,后續再繼續一起深究源代碼;下一節說說依賴注入;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM