註冊的伺服器和中間件共同構成了ASP.NET Core用於處理請求的管道, 這樣一個管道是在我們啟動作為應用宿主的WebHost時構建出來的。要深刻瞭解這個管道是如何被構建出來的,我們就必須對WebHost和它的創建者WebHostBuilder這個重要的對象具有深刻的理解。[ ...
註冊的伺服器和中間件共同構成了ASP.NET Core用於處理請求的管道, 這樣一個管道是在我們啟動作為應用宿主的WebHost時構建出來的。要深刻瞭解這個管道是如何被構建出來的,我們就必須對WebHost和它的創建者WebHostBuilder這個重要的對象具有深刻的理解。[本文已經同步到《ASP.NET Core框架揭秘》之中]
目錄
一、WebHost
WebHostOptions
構建管道的三個步驟
二、WebHostBuilder
WebHost的創建
幾個常用的擴展方法
一、WebHost
顧名思義,WebHost被作為Web應用的宿主,應用的啟動和關閉都是通過啟動或者關閉對應WebHost的方式來實現的。這裡所說的WebHost是對所有實現了IWebHost介面的所有類型及其對應對象的統稱。IWebHost介面具有如下三個基本成員,其中Start方法用於啟動宿主程式。我們編程中通常會調用它的一個擴展方法Run來啟動WebHost,實際上背後調用的其實還是這個Start方法。當WebHost啟動之後,註冊的伺服器變開始了針對請求的監聽,所以WebHost需要具有與伺服器相關的一些特性,這些特性就保存在通過屬性ServerFeatures返回的特性集合中。
1: public interface IWebHost : IDisposable
2: {
3: void Start();
4: IFeatureCollection ServerFeatures { get; }
5: IServiceProvider Services { get; }
6: }
我們多次提到ASP.NET Core管道在構建和進行請求處理過程中廣泛使用到了依賴註入。依賴註入只要體現在:ASP.NET Core框架以及應用程式會根據需要註冊一系列的服務,這些服務會在WebHost啟動的時候被用來創建一個ServiceProvider對象,管道在進行請求處理過程所需的任何服務對象都可以從這個ServiceProvider對象中獲取。IWebHost介面的Services屬性返回的就是這麼一個ServiceProvider對象。
具有如下定義的WebHost類是對IWebHost介面的預設實現,我們預設使用的WebHost就是這麼一個對象。一般來說,WebHost是通過對應的WebHostBuilder創建的,當後者通過調用構造函數創建一個WebHost對象的時候,需要提供四個參數,它們分別是直接註冊到WebHostBuilder上面的服務(appServices)和由此創建的ServiceProvider(hostingServiceProvider),針對WebHost的選項設置(options)和配置(config)。
1: public class WebHost : IWebHost
2: {
3: public IFeatureCollection ServerFeatures { get; }
4: public IServiceProvider Services { get; }
5:
6: public WebHost(
7: IServiceCollection appServices,
8: IServiceProvider hostingServiceProvider,
9: WebHostOptions options,
10: IConfiguration config);
11:
12: public void Dispose();
13: public void Start();
14: }
WebHostOptions
顧名思義,一個WebHostOptions對象為構建的WebHost對象提供一些預定義的選項設置。這些選項設置很重要,它們決定由WebHost構建的管道進行內容載入以及異常處理等方面的行為。至於它具體攜帶著哪些選項設置,我們只需要看看這個類型具有怎樣的屬性成員。
1: public class WebHostOptions
2: {
3: public string ApplicationName { get; set; }
4: public bool DetailedErrors { get; set; }
5: public bool CaptureStartupErrors { get; set; }
6: public string Environment { get; set; }
7: public string StartupAssembly { get; set; }
8: public string WebRoot { get; set; }
9: public string ContentRootPath { get; set; }
10:
11: public WebHostOptions()
12: public WebHostOptions(IConfiguration configuration)
13: }
如下麵的代碼片段所示,WebHostOptions具有七個屬性成員。這些屬性都是可讀可寫的,我們可以調用預設無參構造函數創建一個空的WebHostOptions對象,通過手工為這些屬性賦值的方式來設置對應的選項。除此之外,我們可以將這些選項設置定義在配置中,並利用對應的Configuration對象來創建一個WebHostOptions對象。
構建管道的三個步驟
一般我們開啟了作為應用宿主的WebHost,由註冊的伺服器和中間件構成的整個管道被構建起來,伺服器開始綁定到基地址進行請求的監聽。接下來我們就來著重聊聊WebHost在開啟過程中都做了些什麼。總的來說,WebHost的整個開啟過程大體上可以分為如下三個步驟:
- 註冊服務:獲取Startup對象並利用它完成服務的註冊。
- 中間件註冊:利用獲取的Startup對象完成中間件的註冊。
- 設置並開啟伺服器:獲取註冊到WebHostBuilder上的伺服器併為之設置監聽地址,最後啟動伺服器。
接下來我們按照這個步驟定義一個同名的類型來模式真實WebHost的實現邏輯。如下麵的代碼片段所示,這個模擬的WebHost和真正的WebHost的構造函數具有完全一致的參數列表,我們定義了對應的欄位來保存這些參數值。除此之外,我們會創建一個ApplicationLifetime對象並將其註冊到提供個ServiceCollection,在WebHost開啟和關閉之後我們會利用它發送相應的通知。
1: public class WebHost : IWebHost
2: {
3: private IServiceCollection _appServices;
4: private IServiceProvider _hostingServiceProvider;
5: private WebHostOptions _options;
6: private IConfiguration _config;
7: private ApplicationLifetime _applicationLifetime;
8:
9: public WebHost(IServiceCollection appServices, IServiceProvider hostingServiceProvider, WebHostOptions options, IConfiguration config)
10: {
11: _appServices = appServices;
12: _hostingServiceProvider = hostingServiceProvider;
13: _options = options;
14: _config = config;
15: _applicationLifetime = new ApplicationLifetime();
16: appServices.AddSingleton<IApplicationLifetime>(_applicationLifetime);
17: }
18: …
19: }
20:
我們接下來看WebHost除Start方法之外的其他成員的定義。只讀屬性Services返回一個ServiceProvider對象,我們將在完成所有服務註冊工作之後利用ServiceCollection對象創建這個對象,所以只要實現具有相關的服務註冊,我們就能夠利用它得到對應的服務對象。只讀屬性ServerFeatures返回伺服器的特性集合,而伺服器本身則直接利用上述這個ServiceProvider獲得。當MyWebHost對象因Dispose方法的調用而被回收之後,我們會對ServiceProvider實施回收 工作。在實施回收的前後,我們利用ApplicationLifetime發送相應的信號。
1: public class WebHost : IWebHost
2: {
3: private ApplicationLifetime _applicationLifetime;
4: public IServiceProvider Services { get; private set; }
5: public IFeatureCollection ServerFeatures
6: {
7: get { return this.Services.GetRequiredService<IServer>()?.Features; }
8: }
9: public void Dispose()
10: {
11: _applicationLifetime.StopApplication();
12: (this.Services as IDisposable)?.Dispose();
13: _applicationLifetime.NotifyStopped();
14: }
15: }
16:
真正開啟WebHost的實現體現在如下所示的代碼片段中。我們直接利用WebHostBuilder提供ServiceProvider獲取一個Startup對象,並調用其ConfigureServices方法完成服務的註冊,作為參數的ServiceCollection對象也是由WebHostBuilder提供的。當所有的服務註冊工作完成之後,我們利用最新的ServiceCollection對象創建一個ServiceProvider對象,並利用此對象對Services屬性進行賦值。在後續管道構建過程,以及管道在處理請求過程中所使用的服務均是從這個ServiceProvider中提取的。
1: public class WebHost : IWebHost
2: {
3: private IServiceCollection _appServices;
4: private IServiceProvider _hostingServiceProvider;
5: private WebHostOptions _options;
6: private IConfiguration _config;
7: private ApplicationLifetime _applicationLifetime;
8:
9: public void Start()
10: {
11: //註冊服務
12: IStartup startup = _hostingServiceProvider.GetRequiredService<IStartup>();
13: this.Services = startup.ConfigureServices(_appServices);
14:
15: //註冊中間件
16: Action<IApplicationBuilder> configure = startup.Configure;
17: configure = this.Services.GetServices<IStartupFilter>().Reverse().Aggregate(configure, (next, current) => current.Configure(next));
18: IApplicationBuilder appBuilder = this.Services.GetRequiredService<IApplicationBuilder>();
19: configure(appBuilder);
20:
21: //為伺服器設置監聽地址
22: IServer server = this.Services.GetRequiredService<IServer>();
23: IServerAddressesFeature addressesFeature = server.Features.Get<IServerAddressesFeature>();
24: if (null != addressesFeature && !addressesFeature.Addresses.Any())
25: {
26: string addresses = _config["urls"] ?? "http://localhost:5000";
27: foreach (string address in addresses.Split(';'))
28: {
29: addressesFeature.Addresses.Add(address);
30: }
31: }
32:
33: //啟動伺服器
34: RequestDelegate application = appBuilder.Build();
35: ILogger logger = this.Services.GetRequiredService <ILogger<MyWebHost>>();
36: DiagnosticSource diagnosticSource = this.Services.GetRequiredService<DiagnosticSource>();
37: IHttpContextFactory httpContextFactory = this.Services.GetRequiredService<IHttpContextFactory>();
38: server.Start(new HostingApplication(application, logger, diagnosticSource, httpContextFactory));