為什麼我們可以在Startup這個 “孤零零的” 類中配置依賴註入和管道? 它是什麼時候被實例化並且調用的? 參數中的IServiceCollection services是怎麼來的? 處理管道是怎麼構建起來的? 啟動過程中,系統“默默的”做了哪些準備工作? 上一篇文章講了ASP.NET Core中 ...
為什麼我們可以在Startup這個 “孤零零的” 類中配置依賴註入和管道?
它是什麼時候被實例化並且調用的?
參數中的IServiceCollection services是怎麼來的?
處理管道是怎麼構建起來的?
啟動過程中,系統“默默的”做了哪些準備工作?
上一篇文章講了ASP.NET Core中的依賴註入(系列目錄), 而它的配置是在Startup這個文件中的 ConfigureServices(IServiceCollection services) 方法,而且Startup這個類也沒有繼承任何類或者介面。 深入的想一想,可能會冒出類似上面列出的好多問題,下麵用一幅圖來看透它。
一、整體流程圖
先上圖, 覺得看不清可以點擊看大圖或者下載後放大查看。
圖一 (點擊放大)
二、WebHostBuilder
應用程式在Main方法之後通過調用CreateDefaultBuilder方法創建並配置WebHostBuilder,
1 public class WebHostBuilder : IWebHostBuilder 2 { 3 private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; 4 5 private IConfiguration _config; 6 public IWebHostBuilder UseSetting(string key, string value) 7 { 8 _config[key] = value; 9 return this; 10 } 22 public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices) 23 { 24 if (configureServices == null) 25 { 26 throw new ArgumentNullException(nameof(configureServices)); 27 } 29 _configureServicesDelegates.Add(configureServices); 30 return this; 31 } 32 }
WebHostBuilder存在一個重要的集合① private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; , 通過 ConfigureServices 方法將需要的Action加入進來。
UseSetting是一個用於設置Key-Value的方法, 一些常用的配置均會通過此方法寫入_config中。
三、UseStartup<Startup>()
CreateDefaultBuilder之後調用UseStartup<Startup>(),指定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<IHostingEnvironment>(); 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 )過來的時候你加入他們。
其實在CreateDefaultBuilder方法中的幾個UseXXX的方法也是這樣通過ConfigureServices將對應的Action寫入了configureServicesDelegates, 等待唐僧的到來。
四、WebHostBuilder.Build()
創建並配置好的WebHostBuilder開始通過Build方法創建WebHost了, 首先是BuildCommonServices,
1 private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors) 2 { 3 //...省略... 4 var services = new ServiceCollection(); 5 services.AddSingleton(_hostingEnvironment); 6 services.AddSingleton(_context); 7 //....各種Add.... 9 foreach (var configureServices in _configureServicesDelegates) 10 { 11 configureServices(_context, services); 12 } 14 return services; 15 }
在這個方法里創建了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<IStartup>();
_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
通過 GetRequiredService<IStartup>() 獲取到我們的_startup, 然後調用這個_startup的 ⑤ConfigureServices 方法,這就是我們用於依賴註入的startup類的ConfigureServices方法了。
所以,_applicationServices是根據_applicationServiceCollection 加上我們在_startup中註冊的內容之後重新生成的 IServiceProvider。
EnsureServer()⑥:通過 GetRequiredService<IServer>()獲取Server並配置監聽地址。
var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>(); var builder = builderFactory.CreateBuilder(Server.Features); builder.ApplicationServices = _applicationServices;
獲取到 IApplicationBuilderFactory並通過它⑦創建 IApplicationBuilder,並將上面創建的_applicationServices賦值給它的ApplicationServices,它還有個重要的集合_components
private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
從_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()方法用來開啟一個後臺運行的服務,一些需要後臺運行的操作比如定期刷新緩存等可以放到這裡來。