歡迎光臨
每天分享高質量文章

ASP.NET Core 2.0 : 一張圖看透啟動背後的秘密

作者:FlyLolo

連結:https://www.cnblogs.com/FlyLolo/p/ASPNETCore2_7.html

為什麼我們可以在Startup這個 “孤零零的” 類中配置依賴註入和管道?

它是什麼時候被實體化並且呼叫的?

引數中的IServiceCollection services是怎麼來的?

處理管道是怎麼構建起來的?

啟動過程中,系統“默默的”做了哪些準備工作?


上一篇文章講了ASP.NET Core中的依賴註入(系列目錄http://www.cnblogs.com/FlyLolo/p/ASPNETCore2_0.html), 而它的配置是在Startup這個檔案中的 ConfigureServices(IServiceCollection services) 方法,而且Startup這個類也沒有繼承任何類或者介面。 深入的想一想,可能會冒出類似上面列出的好多問題,下麵用一幅圖來看透它。

一、整體流程圖

先上圖, 覺得看不清可以點選看大圖或者下載後放大檢視。


圖一  (點選放大)

二、WebHostBuilder

應用程式在Main方法之後透過呼叫Create­DefaultBuilder方法建立並配置WebHostBuilder,

 

public class WebHostBuilder : IWebHostBuilder
    {
        private readonly List> _configureServicesDelegates;

        private IConfiguration _config;
        public IWebHostBuilder UseSetting(string key, string value)
        
{
            _config[key] = value;
            return this;
        }
        public IWebHostBuilder ConfigureServices(Action configureServices)
        
{
            if (configureServices == null)
            {
                throw new ArgumentNullException(nameof(configureServices));
            }
            _configureServicesDelegates.Add(configureServices);
            return this;
        }
    }


WebHostBuilder存在一個重要的集合① private readonly List> _configureServicesDelegates; , 透過 ConfigureServices 方法將需要的Action加入進來。


UseSetting是一個用於設定Key-Value的方法, 一些常用的配置均會透過此方法寫入_config中。

三、UseStartup()

Create­DefaultBuilder之後呼叫UseStartup(),指定Startup為啟動類。


public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
        
{
            var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;

            return hostBuilder
                .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
                .ConfigureServices(services =>
                {
                    if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
                    {
                        services.AddSingleton(typeof(IStartup), startupType);
                    }
                    else
                    {
                        services.AddSingleton(typeof(IStartup), sp =>
                        {
                            var hostingEnvironment = sp.GetRequiredService();
                            return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
                        });
                    }
                });
        }


首先獲取Startup類對應的AssemblyName, 呼叫UseSetting方法將其設定為WebHostDefaults.ApplicationKey(“applicationName”)的值。


然後呼叫WebHostBuilder的②ConfigureServices方法,將一個Action寫入WebHostBuilder 的 configureServicesDelegates中。


這個Action的意思就是說,如果這個被指定的類startupType是一個實現了IStartup的類, 那麼將其透過AddSingleton註冊到services 這個ServiceCollection中, 如果不是, 那麼將其“轉換”成 ConventionBasedStartup 這個實現了 IStartup的類後再進行註冊。這裡涉及到一個StartupLoader的LoadMethods()方法,會透過字串的方式查詢“ConfigureServices”、“Configure{ environmentName}Services”這樣的方法。


註意:這裡只是將一個Action寫入了configureServicesDelegates, 而不是已經執行了對IStartup的註冊, 因為這個Action尚未執行,services也還不存在。就像菩薩對八戒說: 八戒(Startup)你先在高老莊等著吧, 將來有個和尚帶領一個取經小分隊(ServiceCollection services )過來的時候你加入他們。


其實在Create­DefaultBuilder方法中的幾個UseXXX的方法也是這樣透過ConfigureServices將對應的Action寫入了configureServicesDelegates, 等待唐僧的到來。

四、WebHostBuilder.Build()

建立並配置好的WebHostBuilder開始透過Build方法建立WebHost了, 首先是BuildCommonServices, 


private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
        
{
            //...省略...
            var services = new ServiceCollection();
            services.AddSingleton(_hostingEnvironment);
            services.AddSingleton(_context);
            //....各種Add....
            foreach (var configureServices in _configureServicesDelegates)
            {
                configureServices(_context, services);
            }
            return services;
        }


在這個方法裡建立了ServiceCollection services(以唐僧為首的取經小分隊), 然後透過各種Add方法註冊了好多內容進去(收了悟空),然後③foreach 之前暫存在configureServicesDelegates中的各個Action,傳入services逐一執行, 將之前需要註冊的內容註冊到services中, 這裡就包括Startup(八戒),註意這裡僅是進行了註冊,而未執行Startup的方法。


處理好的這個services被BuildCommonServices傳回後賦值給 hostingServices,然後 hostingServices經過Clone()生成 applicationServices,再由這個 applicationServices進行GetProviderFromFactory(hostingServices)生成一個 IServiceProvider hostingServiceProvider.經過一系列的處理後,可以建立WebHost了。


var host = new WebHost(
    applicationServices,
    hostingServiceProvider,
    _options,
    _config,
    hostingStartupErrors);

host.Initialize();


將生成的applicationServices 和 hostingServiceProvider作為引數傳遞給新生成的WebHost。接下來就是這個WebHost的 Initialize()。

五、WebHost.Initialize()

WebHost的 Initialize()的主要工作就是BuildApplication()。


EnsureApplicationServices(): 用來處理WebHost的 private IServiceProvider _applicationServices ,④Startup的ConfigureServices方法在這裡被呼叫


_startup = _hostingServiceProvider.GetRequiredService();
_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);

透過 GetRequiredService() 獲取到我們的_startup, 然後呼叫這個_startup的 ⑤ConfigureServices 方法,這就是我們用於依賴註入的startup類的ConfigureServices方法了。


所以,_applicationServices是根據_applicationServiceCollection 加上我們在_startup中註冊的內容之後重新生成的 IServiceProvider。


EnsureServer()⑥:透過 GetRequiredService()獲取Server並配置監聽地址。

var builderFactory = _applicationServices.GetRequiredService();
var builder = builderFactory.CreateBuilder(Server.Features);
builder.ApplicationServices = _applicationServices;


獲取到 IApplicationBuilderFactory並透過它⑦建立 IApplicationBuilder,並將上面建立的_applicationServices賦值給它的ApplicationServices,它還有個重要的集合_components


private readonly IList> _components = new List>();

 

從_components的型別可以看出它其實是中介軟體的集合,是該呼叫我們的_startup的Configure方法的時候了。


先獲取定義的IStartupFilter, ⑧foreach這些IStartupFilter並與_startup的Configure方法一起將配置的中介軟體寫入_components,然後透過 Build()建立RequestDelegate _application,

在Build()中對_components進行處理生成請求處理管道,關於IStartupFilter和生成管道這部分將在下篇文章進行詳細說明。

六、WebHost.Run()

WebHost建立完畢, 最後一步就是Run起來了,WebHost的Run()會呼叫它的方法StartAsync()


public virtual async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken))
{
    //......var hostingApp = new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory);
    await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
    _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
    //.....
}


在之前的文章中我們知道,請求是經過 Server監聽=>處理成httpContext=>Application處理,所以這裡首先傳入上面建立的_application和一個httpContextFactory來⑨生成一個HostingApplication,並將這個HostingApplication傳入Server的StartAsync(), 當Server監聽到請求之後, 後面的工作由HostingApplication來完成。


⑩hostedServiceExecutor.StartAsync()方法用來開啟一個後臺執行的服務,一些需要後臺執行的操作比如定期掃清快取等可以放到這裡來。

七、更新

感謝dudu的留言,去github上看了一下WebHost的最新原始碼,BuildApplication()不再包含EnsureApplicationServices()的呼叫,並且轉移到了WebHost.StartAsync() 中進行; 

WebHost.Initialize() 中由原本呼叫BuildApplication()改為呼叫原本放在BuildApplication()中呼叫的EnsureApplicationServices()。


透過VS載入符號的方式除錯獲取到的WebHost仍是原來的版本,即使刪除下載的檔案後再次重新獲取也一樣, 應該是和新建專案預設取用的依賴版本有關。

贊(0)

分享創造快樂