ASP.NET Core技術研究-探祕Host主機啓動過程

當咱們將原有ASP.NET 應用程序升級遷移到ASP.NET Core以後,咱們發現代碼工程中多了兩個類Program類和Startup類。git

接下來咱們詳細探祕一下通用主機Host的啓動過程。github

1、Program類的Main函數入口web

Program類最重要的功能就是啓動主機,這裏有一個主機的概念,是ASP.NET Core全新引入的。json

主機負責應用程序啓動和生存期管理。 同時,主機也是封裝應用程序資源的對象:windows

  • 依賴注入 (DI)
  • Logging
  • Configuration
  • IHostedService 實現

啓動主機時,它在 DI 容器中找到 IHostedService 的每一個實現,而後調用 IHostedService.StartAsync。 在 web 應用中,其中一個 IHostedService 的實現是啓動 HTTP 服務器實現的 web 服務。這裏的HTTP服務器默認是Kestrel。服務器

即:ASP.NET Core主機啓動時,會啓動一個HTTP服務器,默認是Kestrel。啓動後監聽並響應某個端口的HTTP請求。app

咱們繼續看Program類的代碼: ide

  

   從上述代碼能夠看到,Main函數中首先調用CreateHostBuilder方法,返回一個IHostBuilder。而後調用IHostBuilder.Build()方法完成函數

2、Host.CreateDefaultBuilder(args): 構造IHostBuilder的默認實現HostBuilderoop

   在CreateHostBuilder方法內部,首先調用了Host.CreateDefaultBuilder構造了一個HostBuilder,這個咱們先看下源碼,看看到底Host類內部作了什麼操做:

   https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/Host.cs

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;
        }

  從上述代碼中,能夠看到CreateDefaultBuilder內部構造了一個HostBuilder,同時設置了:

  • 將內容根目錄(contentRootPath)設置爲由 GetCurrentDirectory 返回的路徑。
  • 經過如下源加載主機配置
    • 環境變量(DOTNET_前綴)配置
    • 命令行參數配置
  •      經過如下對象加載應用配置
    • appsettings.json 
    • appsettings.{Environment}.json
    • 密鑰管理器 當應用在 Development 環境中運行時
    • 環境變量
    • 命令行參數
  •      添加日誌記錄提供程序
    • 控制檯
    • 調試
    • EventSource
    • EventLog( Windows環境下)
  • 當環境爲「開發」時,啓用範圍驗證和依賴關係驗證。

   以上構造完成了HostBuilder,針對ASP.NET Core應用,代碼繼續調用了HostBuilder.ConfigureWebHostDefaults方法。

3、IHostBuilder.ConfigureWebHostDefaults:經過GenericWebHostBuilderHostBuilder增長ASP.NET Core的運行時設置

    構造完成HostBuilder以後,針對ASP.NET Core應用,繼續調用了HostBuilder.ConfigureWebHostDefaults方法。這是一個ASP.NET Core的一個擴展方法:

    

      咱們繼續看ConfigureWebHostDefaults擴展方法內部作了哪些事情:

      源碼鏈接: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內部

     代碼鏈接:https://github.com/dotnet/aspnetcore/blob/release/3.1/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs

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運行時設置。

   能夠參考代碼:https://github.com/dotnet/aspnetcore/blob/release/3.1/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs

   。。。

   先看到這,讓咱們回到ConfigureWebHostDefaults:

   將上面兩段代碼合併一下進行理解:ConfigureWebHostDefaults作了兩件事情:

   1. 擴展IHostBuilder增長ConfigureWebHost,引入IWebHostBuilder的實現GenericWebHostBuilder,將已有的HostBuilder增長ASP.NET Core運行時的設置。

   2. 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();
        }

      內部實現了:

  3. 返回ConfigureWebHostDefaults代碼中的configure(webHostBuilder):執行Program類中的webBuilder.UseStartup<Startup>();

  第三章節中,以上過程完成了IHostBuilder.ConfigureWebHostDefaults,經過GenericWebHostBuilder對HostBuilder增長ASP.NET Core的運行時設置。

  接下來繼續Build和Run的過程。

4、CreateHostBuilder(args).Build().Run();

  CreateHostBuilder返回的IHostBuilder,咱們經過代碼Debug,看一下具體的類型:Microsoft.Extensions.Hosting.HostBuilder,這樣進一步驗證了前三個章節的代碼。

  

  1. 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:建立依賴注入服務提供程序,  即依賴注入容器

  2. Run的過程

     咱們先經過Debug,看一下Host的信息:Microsoft.Extensions.Hosting.Internal.Host

     

      這個Run方法也是一個擴展方法:HostingAbstractionsHostExtensions.Run

      代碼連接:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Abstractions/src/HostingAbstractionsHostExtensions.cs

      

     其實內部轉調的仍是Host.StartAsync方法,在內部啓動了DI依賴注入容器中全部註冊的服務。

     代碼連接:https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/Internal/Host.cs

     

   整個Host主機的啓動過程仍是很是複雜的,咱們只是簡單的在代碼層面研究了一遍,感受只是有了個大體的輪廓,具體怎麼執行的,是否是如上面代碼的解釋,還須要深刻繼續研究。

   接下來下一篇文章準備把源碼單步調試看看。加深對ASP.NET Core底層技術原理的理解,只有理解了底層技術實現,咱們在應用層才能更好、正確的使用。

   

周國慶

2020/4/6

相關文章
相關標籤/搜索