程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 學習ASP.NET Core, 怎能不了解請求處理管道[6]: 管道是如何隨著WebHost的開啟被構建出來的?,asp.netwebhost

學習ASP.NET Core, 怎能不了解請求處理管道[6]: 管道是如何隨著WebHost的開啟被構建出來的?,asp.netwebhost

編輯:關於.NET

學習ASP.NET Core, 怎能不了解請求處理管道[6]: 管道是如何隨著WebHost的開啟被構建出來的?,asp.netwebhost


注冊的服務器和中間件共同構成了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));
  39:  
  40:         //對外發送通知
  41:         _applicationLifetime.NotifyStarted();
  42:     }
  43: }
  44:  

當服務注冊結束並成功創建出ServiceProvider之後,接下來的工作就是注冊中間件了。通過上面的介紹我們知道,中間件的注冊既可以利用Startup來完成,也可以利用注冊的StartupFilter來實現,為此我們利用最新構建的ServiceProvider獲取所有注冊的StartupFilter,並結合之前提取的Startup對象創建了一個用於注冊中間的委托鏈(最終體現為一個Action<IApplicationBuilder>對象)。我們最終執行這個委托鏈完成了對所有中間件的注冊,執行過程中作為參數的ApplicationBuilder對象同樣是通過ServiceProvider提取出來的。

再此之後,我們利用ServiceProvider提取出注冊在WebHostBuiler上的服務器。如果服務器的監聽地址尚未指定,我們在開啟服務器之前必須指定。通過前面對服務器的介紹,我們知道監聽地址保存在服務器的一個名為ServerAddressesFeature的特性中,而用戶設置的監聽地址則保存在配置中,對應的Key為“urls”,所以我們將從配置中提取的地址列表添加到ServerAddressesFeature特性中。如果監聽地址不曾配置,我們會為之指定一個默認的地址,即“http://localhost:5000”。

一切就緒的服務器通過調用Start方法開啟,該方法接收一個HttpApplication對象作為參數。通過前面的介紹我們知道這個HttpApplication對象可以視為對所有注冊中間件和應用的封裝,服務器將接收到的請求傳遞給它作後續處理。我們默認創建的HttpApplication是一個HostingApplication對象,而構建過程中需要提供四個對象,它們分別是代表中間件鏈表的RequestDelegate對象,用於日志記錄和診斷的Logger和DiagnosticSource,以及用來創建HTTP上下文的HttpContextFactory,除了第一個通過調用ApplicationBuilder的Build方法創建之外,其余的都是通過ServiceProvider提取的。在服務器被成功開啟之後,我們利用ApplicationLifetime對外發送應用啟動的通知。

二、WebHostBuilder

顧名思義,WebHostBuilder就是WebHost的創建者,所謂的WebHostBuilder是對所有實現了IWebHostBuilder接口的類型以及對應對象的統稱。如下面的代碼片段所示,IWebHostBuilder接口除了用來創建WebHost的核心方法Build之外,還具有其他一些額外的方法。

   1: public interface IWebHostBuilder
   2: {
   3:     IWebHost Build();
   4:     IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices); 
   5:     IWebHostBuilder UseLoggerFactory(ILoggerFactory loggerFactory);
   6:     IWebHostBuilder ConfigureLogging(Action<ILoggerFactory> configureLogging);
   7:     string GetSetting(string key);
   8:     IWebHostBuilder UseSetting(string key, string value);
   9: }

ConfigureServices方法讓我們可以直接將我們所需的服務注冊到WebHostBuilder上面。ASP.NET Core具有兩種注冊服務的途徑,一種是將服務注冊實現在啟動類的ConfigureServices方法中,另一種服務注冊的方式就是調用這個方法。對於前者,服務實際上是在開啟WebHost的時候調用Startup對象的ConfigureServices進行注冊的;至於後者,注冊的服務將直接提供給創建的WebHost。UseLoggerFactory 和ConfigureLogging方法與日志記錄有關,前者幫助我們設置一個默認的LoggerFactory,後者則對LoggerFactory進行相關設置,最重要的設置就是添加相應的LoggerProvider。GetSetting和UseSetting以鍵值對的形式獲取和設置一些配置。

WebHost的創建

ASP.NET Core定義了一個名為WebHostBuilder的類型作為對IWebHostBuilder接口的默認實現,我們同樣采用定義模擬類型的形式來說明WebHostBuilder創建WebHost的實現原理。我們將這個模擬類型命名為,如下的代碼片段展示了除Build方法之外的所有成員的定義。

   1: public class WebHostBuilder : IWebHostBuilder
   2: {
   3:     private List<Action<ILoggerFactory>> _configureLoggingDelegates = new List<Action<ILoggerFactory>>();
   4:     private List<Action<IServiceCollection>> _configureServicesDelegates = new List<Action<IServiceCollection>>();
   5:     private ILoggerFactory _loggerFactory = new LoggerFactory();
   6:     private IConfiguration _config = new ConfigurationBuilder().AddEnvironmentVariables("ASPNETCORE_").Build();
   7:  
   8:     public IWebHostBuilder ConfigureLogging(Action<ILoggerFactory> configureLogging)
   9:     {
  10:         _configureLoggingDelegates.Add(configureLogging);
  11:         return this;
  12:     }
  13:  
  14:     public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices)
  15:     {
  16:         _configureServicesDelegates.Add(configureServices);
  17:         return this;
  18:     }
  19:  
  20:     public string GetSetting(string key)
  21:     {
  22:         return _config[key];
  23:     }
  24:  
  25:     public IWebHostBuilder UseLoggerFactory(ILoggerFactory loggerFactory)
  26:     {
  27:         _loggerFactory = loggerFactory;
  28:         return this;
  29:     }
  30:  
  31:     public IWebHostBuilder UseSetting(string key, string value)
  32:     {
  33:         _config[key] = value;
  34:         return this;
  35:     }
  36:     ...
  37: }
  38:  

如上面的代碼片段所示,我們創建了一個Configuration類型的字段(_config)來體現應用默認使用的配置,它默認采用環境變量(用於過濾環境變量的前綴為“ASPNETCORE_”)作為配置源,GetSetting和UseSetting方法操作的均為這個對象。另一個字段_loggerFactory表示默認使用的LoggerFactory,UseLoggerFactory方法指定的LoggerFactory用來對這個字段進行賦值。ConfigureLogging和ConfigureServices方法具有類似的定義,調用它們提供的委托對象都保存在一個集合之中,以待後用。

我們實現WebHostBuilder的核心方法Build來創建一個WebHost對象。通過上面的定義我們知道一個WebHostBuilder能夠最終運行起來需要從ServiceProvider提供很多必需的服務,而這些服務最初都必需通過WebHostBuilder來注冊,所以Build方法除了調用構造函數創建並返回一個WebHost對象之外,余下的工作就是注冊這些必需的服務。我們可以簡單列一列那些服務是必需的,如下所示的是一個不完全列表。

  • 用於注冊服務和中間件的Startup對象。
  • 用來創建Logger的LoggerFactory對象
  • 構建中間件鏈表的ApplicationBuilder對象
  • 創建HTTP上下文的HttpContextFactory對象
  • 用戶實現診斷功能的DiagnosticSource對象
  • 用來保存承載環境的HostingEnvironment對象

如下所示的定義在WebHostBuilder中的Build方法的定義。在這個方法中,我們按照上述這些系統服務以及用戶服務(通過調用ConfigureServices方法注冊的服務)的注冊之後,創建並返回了一個WebHost對象。

   1: public class WebHostBuilder : IWebHostBuilder
   2: {
   3:     private List<Action<ILoggerFactory>> _configureLoggingDelegates = new List<Action<ILoggerFactory>>();
   4:     private List<Action<IServiceCollection>> _configureServicesDelegates = new List<Action<IServiceCollection>>();
   5:     private ILoggerFactory _loggerFactory = new LoggerFactory();
   6:     private IConfiguration _config = new ConfigurationBuilder().AddInMemoryCollection().Build();
   7:  
   8:     public IWebHost Build()
   9:     {
  10:         //根據配置創建WebHostOptions
  11:         WebHostOptions options = new WebHostOptions(_config);
  12:  
  13:         //注冊服務IStartup
  14:         IServiceCollection services = new ServiceCollection();
  15:         if (!string.IsNullOrEmpty(options.StartupAssembly))
  16:         {
  17:             Type startupType = StartupLoader.FindStartupType(options.StartupAssembly, options.Environment);
  18:             if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType))
  19:             {
  20:                 services.AddSingleton(typeof(IStartup), startupType);
  21:             }
  22:             else
  23:             {
  24:                 services.AddSingleton<IStartup>(_ => new ConventionBasedStartup(StartupLoader.LoadMethods(_, startupType, options.Environment)));
  25:             }
  26:         }
  27:  
  28:         //注冊ILoggerFactory
  29:         foreach (var configureLogging in _configureLoggingDelegates)
  30:         {
  31:             configureLogging(_loggerFactory);
  32:         }
  33:         services.AddSingleton<ILoggerFactory>(_loggerFactory);
  34:  
  35:         //注冊服務IApplicationBuilder,DiagnosticSource和IHttpContextFactory
  36:         services
  37:             .AddSingleton<IApplicationBuilder>(_ => new ApplicationBuilder(_))
  38:             .AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore"))
  39:             .AddSingleton<IHttpContextFactory, HttpContextFactory>()
  40:             .AddOptions()
  41:             .AddLogging()
  42:             .AddSingleton<IHostingEnvironment, HostingEnvironment>()
  43:             .AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();          
  44:                       
  45:         //注冊用戶調用ConfigureServices方法設置的服務
  46:         foreach (var configureServices in _configureServicesDelegates)
  47:         {
  48:             configureServices(services);
  49:         }
  50:  
  51:         //創建MyWebHost
  52:         return new WebHost(services, services.BuildServiceProvider(), options, _config);
  53:     }  
  54: }
  55:  

雖然上面提供的WebHost和WebHostBuilder僅僅是WebHost和WebHostBuilder的模擬類。為了讓讀更加易於理解,我們刻意剔除了很多細節的東西,但是兩者從實現原理角度來講是完全一致的。不僅如此,我們自定義的這兩個類型甚至可以執行運行的。

幾個常用的擴展方法

WebHostBuilder在內部使用了配置,環境變量是默認采用的配置源,它的兩個方法GetSetting和UseSetting以鍵值對的形式實現對配置項的獲取和設置。除了UseSettings方法之外,我們還可以調用WebHostBuilder如下這個擴展方法UseConfiguration來進行配置項的設置,這個方法會將保存在指定Configuration中的配置原封不動地拷貝過來,它最終調用的依舊是UseSettings方法。

   1: public static class HostingAbstractionsWebHostBuilderExtensions
   2: {
   3:     public static IWebHostBuilder UseConfiguration(this IWebHostBuilder hostBuilder, IConfiguration configuration);
   4: }

WebHostBuilder在創建WebHost的時候需要提供一個WebHostOptions對象,該對象最初是根據當前配置創建的。為了方便設置針對WebHostOptions的配置項,ASP.NET Core為我們定義了如下一系列的擴展方法,這些方法最終調用的也是這個UseSettings方法。

   1: public static class HostingAbstractionsWebHostBuilderExtensions
   2: {
   3:     public static IWebHostBuilder CaptureStartupErrors(this IWebHostBuilder hostBuilder, bool captureStartupErrors);
   4:     public static IWebHostBuilder UseContentRoot(this IWebHostBuilder hostBuilder, string contentRoot);
   5:     public static IWebHostBuilder UseEnvironment(this IWebHostBuilder hostBuilder, string environment);
   6:     public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, string startupAssemblyName);
   7:     public static IWebHostBuilder UseWebRoot(this IWebHostBuilder hostBuilder, string webRoot);
   8:     public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls);
   9: }
  10:  

雖然服務器是必需的,但是WebHostBuilder並沒有專門定義一個用於注冊服務的方法,這是因為服務器也是作為一項基本的服務進行注冊的。但是我們可以調用如下一個擴展方法UseServer實現針對服務器的注冊,至於另一個擴展方法UseUrls,我們可以調用它來為注冊的服務器設置監聽地址。

   1: public static class HostingAbstractionsWebHostBuilderExtensions
   2: {
   3:     public static IWebHostBuilder UseServer(this IWebHostBuilder hostBuilder, IServer server);
   4:     public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls);
   5: }

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved