超碰人人人人人,亚洲AV午夜福利精品一区二区,亚洲欧美综合区丁香五月1区,日韩欧美亚洲系列

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

學(xué)習(xí)ASP.NET Core,怎能不了解請(qǐng)求處理管道:中間件究竟是個(gè)什么東西?

freeflydom
2025年1月16日 10:49 本文熱度 380

ASP.NET Core管道雖然在結(jié)構(gòu)組成上顯得非常簡(jiǎn)單,但是在具體實(shí)現(xiàn)上卻涉及到太多的對(duì)象,所以我們?cè)?“通過重建Hosting系統(tǒng)理解HTTP請(qǐng)求在ASP.NET Core管道中的處理流程”(上篇、中篇下篇) 中圍繞著一個(gè)經(jīng)過極度簡(jiǎn)化的模擬管道講述了真實(shí)管道構(gòu)建的方式以及處理HTTP請(qǐng)求的流程。在本系列 中,我們會(huì)還原構(gòu)建模擬管道時(shí)可以舍棄和改寫的部分,向讀者朋友們呈現(xiàn)一個(gè)真是的HTTP請(qǐng)求處理管道。 ASP.NET Core 的請(qǐng)求處理管道由一個(gè)服務(wù)器與一組有序排列的中間件構(gòu)成,前者僅僅完成請(qǐng)求監(jiān)聽、接收和響應(yīng)這些與底層網(wǎng)絡(luò)相關(guān)的工作,至于請(qǐng)求接收之后和響應(yīng)之前的所有工作都交給中間件來完成。ASP.NET Core的中間件通過一個(gè)類型Func<RequestDelegate, RequestDelegate>的委托對(duì)象來表示,而RequestDelegate也是一個(gè)委托,它代表一項(xiàng)請(qǐng)求處理任務(wù)。 [本文已經(jīng)同步到《ASP.NET Core框架揭秘》之中]

目錄
一、RequestDelegate
二、HttpContext
    FeatureCollection
    DefaultHttpContext
    HttpContextFactory
三、ApplicationBuilder
    ApplicationBuilderFactory
    中間件類型
    中間件類型的注冊(cè)

一、RequestDelegate

服務(wù)器接受到抵達(dá)的HTTP請(qǐng)求之后會(huì)構(gòu)建一個(gè)描述當(dāng)前請(qǐng)求的原始上下文,服務(wù)器的類型決定了這個(gè)原始上下文的類型,比如在我們模擬管道默認(rèn)采用的HttpListenerServer由于采用HttpListener來監(jiān)聽、接收并響應(yīng)請(qǐng)求,所以它對(duì)應(yīng)的原始上下文是一個(gè)HttpListenerContext對(duì)象。但是對(duì)于管道的后續(xù)部分,即由注冊(cè)的中間件構(gòu)建的鏈表,它們需要采用統(tǒng)一的方式來處理請(qǐng)求,所以服務(wù)器最終會(huì)根據(jù)原始的上下文來創(chuàng)建一個(gè)抽象的HTTP上下文,后者通過抽象類HttpContext來表示。

我們不僅可以利用這個(gè)HttpContext獲取描述當(dāng)前請(qǐng)求的上下文信息,同樣可以利用它來實(shí)現(xiàn)對(duì)響應(yīng)的控制。針對(duì)當(dāng)前請(qǐng)求的任何處理操作總是在這么一個(gè)上下文中進(jìn)行,所以一項(xiàng)請(qǐng)求處理任務(wù)完全可以抽象成一個(gè)類型Func<HttpContext,Task>的委托來表示,實(shí)際上具有如下定義的RequestDelegate委托具有類似的定義。

   1: public delegate Task RequestDelegate(HttpContext context); 

每個(gè)中間件都承載著獨(dú)立的請(qǐng)求處理任務(wù),它本質(zhì)上也體現(xiàn)了在當(dāng)前HttpContext下針對(duì)請(qǐng)求的處理操作,那么為什么中間件不直接通過一個(gè)RequestDelegate對(duì)象來表示,而是表示為一個(gè)類型為Func<RequestDelegate, RequestDelegate>的委托對(duì)象呢?原因很簡(jiǎn)單,中間件并不孤立地存在,所有注冊(cè)的中間件最終會(huì)根據(jù)注冊(cè)的先后順序組成一個(gè)鏈表,每個(gè)中間件不僅僅需要完成各自的請(qǐng)求處理任務(wù)外,還需要驅(qū)動(dòng)鏈表中的下一個(gè)中間件。

如上圖所示,對(duì)于一個(gè)由多個(gè)Func<RequestDelegate, RequestDelegate>對(duì)象組成的中間鏈表來說,某個(gè)中間件會(huì)將后一個(gè)Func<RequestDelegate, RequestDelegate>對(duì)象的返回值作為輸入,而自身的返回值則作為前一個(gè)中間件的輸入。某個(gè)中間件執(zhí)行之后返回的RequestDelegate對(duì)象不僅僅體現(xiàn)了自身對(duì)請(qǐng)求的處理操作,而是體現(xiàn)了包含自己和后續(xù)中間件一次對(duì)請(qǐng)求的處理。那么對(duì)于第一個(gè)中間件來說,它執(zhí)行后返回的RequestDelegate對(duì)象實(shí)際上體現(xiàn)了整個(gè)應(yīng)用對(duì)請(qǐng)求的處理邏輯。

二、 HttpContext

對(duì)當(dāng)前上下文的抽象解除了管道對(duì)具體服務(wù)器類型的依賴, 這使我們可以為ASP.NET Core應(yīng)用自由地選擇承載(Hosting)方式,而不是像傳統(tǒng)的ASP.NET應(yīng)用一樣只能寄宿在IIS之中。抽象HTTP上下文的目的是為了實(shí)現(xiàn)對(duì)請(qǐng)求處理流程的抽象,只有這樣我們才能將針對(duì)請(qǐng)求的某項(xiàng)操作體現(xiàn)在一個(gè)標(biāo)準(zhǔn)的中間件上,有了這個(gè)這個(gè)標(biāo)準(zhǔn)化的中間件才有所謂的請(qǐng)求處理管道。

ASP.NET Core通過具有如下所示的HttpContext類來表示這么一個(gè)抽象的HTTP上下文。對(duì)于一個(gè)HttpContext對(duì)象來說,它的核心體現(xiàn)在用于描述請(qǐng)求和響應(yīng)的Request和Response屬性之上。除此之外,我們還可以通過它獲取與當(dāng)前請(qǐng)求相關(guān)的其他上下文信息,比如用來控制用戶認(rèn)證的AuthenticationManager對(duì)象和代表當(dāng)前請(qǐng)求用戶的ClaimsPrincipal對(duì)象,以及描述當(dāng)前HTTP連接的ConnectionInfo對(duì)象和用于控制WebSocket的WebSocketManager。我們可以獲取并控制當(dāng)前會(huì)話,也可以獲取或者設(shè)置調(diào)試追蹤的ID。

   1: public abstract class HttpContext
   2: {
   3:  
   4:     public abstract HttpRequest     Request { get; }
   5:     public abstract HttpResponse    Response { get; }
   6:  
   7:     public abstract AuthenticationManager            Authentication { get; }
   8:     public abstract ClaimsPrincipal                  User { get; set; }
   9:     public abstract ConnectionInfo                   Connection { get; } 
  10:     public abstract WebSocketManager                 WebSockets { get; } 
  11:     public abstract ISession                         Session { get; set; } 
  12:     public abstract string                           TraceIdentifier { get; set; }
  13:     public abstract CancellationToken                RequestAborted { get; set; }  
  14:     public abstract IDictionary<object, object>      Items { get; set; }  
  15:  
  16:     public abstract IServiceProvider                RequestServices { get; set; }
  17:     public abstract IFeatureCollection              Features { get; }
  18: }

當(dāng)需要中指對(duì)請(qǐng)求的處理時(shí),我們可以通過為RequestAborted屬性設(shè)置一個(gè)CancellationToken對(duì)象從而將終止通知發(fā)送給管道。如果需要對(duì)整個(gè)管道共享一些與當(dāng)前上下文相關(guān)的數(shù)據(jù),我們可以將它保存在通過Items屬性表示的字典中。我們一再提到依賴注入被廣泛地應(yīng)用ASP.NET Core管道中,HttpContext的RequestServices屬性返回的根據(jù)在應(yīng)用啟動(dòng)時(shí)注冊(cè)的服務(wù)而創(chuàng)建的ServiceProvider。只要相應(yīng)的服務(wù)被預(yù)先注冊(cè)到指定的服務(wù)接口上,我們就可能利用這個(gè)ServiceProvider根據(jù)這個(gè)接口得到對(duì)應(yīng)的服務(wù)對(duì)象。

   1: public abstract class HttpRequest
   2: {
   3:     public abstract HttpContext                    HttpContext { get; }
   4:     public abstract string                         Method { get; set; }
   5:     public abstract string                         Scheme { get; set; }
   6:     public abstract bool                           IsHttps { get; set; }
   7:     public abstract HostString                     Host { get; set; }
   8:     public abstract PathString                     PathBase { get; set; }
   9:     public abstract PathString                     Path { get; set; }
  10:     public abstract QueryString                    QueryString { get; set; }
  11:     public abstract IQueryCollection               Query { get; set; }
  12:     public abstract string                         Protocol { get; set; }
  13:     public abstract IHeaderDictionary              Headers { get; } >
  14:     public abstract IRequestCookieCollection       Cookies { get; set; }
  15:     public abstract string                         ContentType { get; set; }
  16:     public abstract Stream                         Body { get; set; }
  17:     public abstract bool                           HasFormContentType { get; }
  18:     public abstract IFormCollection                Form { get; set; }
  19:  
  20:     public abstract Task<IFormCollection>         ReadFormAsync(CancellationToken cancellationToken);
  21: }

如上所示的是抽象類HttpRequest是對(duì)HTTP請(qǐng)求的描述,它是HttpContext的只讀屬性Request的返回類型。我們可以利用這個(gè)對(duì)象獲取到描述當(dāng)前請(qǐng)求的各種相關(guān)信息,比如請(qǐng)求的協(xié)議(HTTP或者HTTPS)、HTTP方法、地址,也可以獲取代表請(qǐng)求的HTTP消息的首部和主體。

在了解了表示請(qǐng)求的抽象類HttpRequest之后,我們?cè)賮碚J(rèn)識(shí)一個(gè)與之相對(duì)的用于描述響應(yīng)HttpResponse類型。如下面的代碼片斷所示,HttpResponse依然是一個(gè)抽象類,我們可以通過定義在它之上的屬性和方法來控制對(duì)請(qǐng)求的響應(yīng)。從原則上講,我們對(duì)請(qǐng)求的所做的任意類型的響應(yīng)都可以利用它來說實(shí)現(xiàn)。當(dāng)我們通過表示當(dāng)前上下文的HttpContext對(duì)象得到表示響應(yīng)的HttpResponse之后,我們不僅僅可以將希望的內(nèi)容寫入響應(yīng)消息的主體,還可以設(shè)置響應(yīng)狀態(tài)碼以及添加相應(yīng)的首部。

   1: public abstract class HttpResponse
   2: {
   3:     public abstract HttpContext           HttpContext { get; }
   4:     public abstract int                   StatusCode { get; set; }
   5:     public abstract IHeaderDictionary     Headers { get; }
   6:     public abstract Stream                Body { get; set; }
   7:     public abstract long?                 ContentLength { get; set; }
   8:     public abstract IResponseCookies      Cookies { get; }
   9:     public abstract bool                  HasStarted { get; }
  10:  
  11:     public abstract void OnStarting(Func<object, Task> callback, object state);
  12:     public virtual void OnStarting(Func<Task> callback);
  13:     public abstract void OnCompleted(Func<object, Task> callback, object state);
  14:     public virtual void RegisterForDispose(IDisposable disposable);
  15:     public virtual void OnCompleted(Func<Task> callback);
  16:     public virtual void Redirect(string location);
  17:     public abstract void Redirect(string location, bool permanent);
  18: }

FeatureCollection

HttpContext的另一個(gè)只讀屬性Features返回一組“特性”對(duì)象。在ASP.NET Core管道式處理設(shè)計(jì)中,特性是一個(gè)非常重要的概念,特性是實(shí)現(xiàn)抽象化HttpContext的途徑。具體來說,服務(wù)器在接收到請(qǐng)求之后會(huì)創(chuàng)建一個(gè)由自身類型決定的原始的上下文,管道不僅僅利用這個(gè)原始上下文來獲取與請(qǐng)求相關(guān)的信息,它對(duì)請(qǐng)求的最終響應(yīng)實(shí)際上也是通過這個(gè)原始上下文來完成的。所以對(duì)一個(gè)HttpContext對(duì)象來說,由它描述的上下文信息不僅僅來源于這個(gè)原始的上下文,我們針對(duì)HttpContext所做的任何響應(yīng)操作最終都需要分發(fā)給這個(gè)原始上下文來完成, 否則是不會(huì)生效的。抽象的HttpContext和原始上下文之間的“雙向綁定”究竟是如何實(shí)現(xiàn)的呢?

這個(gè)所謂的“雙向綁定”即使其實(shí)很簡(jiǎn)單。當(dāng)原始上下文被創(chuàng)建出來之后,服務(wù)器會(huì)將它封裝成一系列標(biāo)準(zhǔn)的特性對(duì)象,HttpContext正是對(duì)這些特性對(duì)象的封裝。一般來說,這些特性對(duì)象所對(duì)應(yīng)的類型均實(shí)現(xiàn)了某個(gè)預(yù)定義的標(biāo)準(zhǔn)接口,接口中不僅僅定義相應(yīng)的屬性來讀寫原始上下文中描述的信息,還定義了相應(yīng)的方法來操作原始上下文。HttpContext的屬性Features返回的就是這組特性對(duì)象的集合,它的返回類型為IFeatureCollection,我們將實(shí)現(xiàn)了該接口的類型以及對(duì)應(yīng)的對(duì)象統(tǒng)稱為FeatureCollection。

   1: public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>>
   2: {
   3:     TFeature Get<TFeature>();
   4:     void Set<TFeature>(TFeature instance);
   5:  
   6:     bool       IsReadOnly { get; }
   7:     object     this[Type key] { get; set; }
   8:     int        Revision { get; }
   9:  }

一個(gè)FeatureCollection對(duì)象本質(zhì)上就是一個(gè)Key和Value分別為Type和Object類型的字段,話句話說,特性對(duì)象通過對(duì)應(yīng)的接口類型注冊(cè)到HttpContext之上。我們通過調(diào)用Set方法將一個(gè)特性對(duì)象針對(duì)指定的類型(一般為特性接口)注冊(cè)到這個(gè)字典對(duì)象上,并通過Get方法根據(jù)注冊(cè)的類型獲取它。特性對(duì)象的注冊(cè)和獲取也可以利用定義的索引來完成。如果IsReadOnly屬性返回True,我們將不能注冊(cè)新的特性或者修改已經(jīng)注冊(cè)的特性。 整數(shù)類型的之都屬性Revision可以視為整個(gè)FeatureCollection對(duì)象的版本,不論是采用何種方式注冊(cè)新的特性還是修改現(xiàn)有的特性,這個(gè)屬性的值都將改變。

具有如下定義的FeatureCollection類實(shí)現(xiàn)了IFeatureCollection接口,我們默認(rèn)使用的FeatureCollection就是這么一個(gè)類型的對(duì)象。FeatureCollection具有兩個(gè)構(gòu)造函數(shù)重載,默認(rèn)無參構(gòu)造函數(shù)幫助我們創(chuàng)建一個(gè)空的特性集合,另一個(gè)構(gòu)造函數(shù)則需要指定一個(gè)FeatureCollection對(duì)象來提供默認(rèn)特性。對(duì)于采用第二個(gè)構(gòu)造函數(shù)重載創(chuàng)建的 FeatureCollection對(duì)象來說,當(dāng)我們通過指定某個(gè)特性接口類型試圖獲取對(duì)應(yīng)的特性對(duì)象時(shí),如果對(duì)應(yīng)的特性沒有注冊(cè)到當(dāng)前FeatureCollection對(duì)象上,而是注冊(cè)到提供默認(rèn)特性的FeatureCollection對(duì)象上,后者將會(huì)提供最終的特性。

   1: public class FeatureCollection : IFeatureCollection
   2: {   
   3:     //其他成員
   4:     public FeatureCollection();
   5:     public FeatureCollection(IFeatureCollection defaults);
   6: }

對(duì)于FeatureCollection類型來說,它 的IsReadOnly總是返回False,所以它永遠(yuǎn)是可讀可寫的。對(duì)于調(diào)用默認(rèn)無參構(gòu)造函數(shù)創(chuàng)建的FeatureCollection對(duì)象來說,它 的Revision默認(rèn)返回零。如果我們通過指定另一個(gè)FeatureCollection對(duì)象為參數(shù)調(diào)用第二個(gè)構(gòu)造函數(shù)來創(chuàng)建一個(gè)FeatureCollection對(duì)象,前者的Revision屬性值將成為后者同名屬性的默認(rèn)值。不論我們采用何種形式(調(diào)用Set方法或者索引)添加一個(gè)新的特性或者改變了一個(gè)已經(jīng)注冊(cè)的特性,F(xiàn)eatureCollection對(duì)象的Revision屬性都將自動(dòng)遞增。上述的這些關(guān)于FeatureCollection的特性都體現(xiàn)在如下所示的代碼片段中。

   1: FeatureCollection defaults = new FeatureCollection();
   2: Debug.Assert(defaults.Revision == 0);
   3:  
   4: defaults.Set<IFoo>(new Foo());
   5: Debug.Assert(defaults.Revision == 1);
   6:  
   7: defaults[typeof(IBar)] = new Bar();
   8: Debug.Assert(defaults.Revision == 2);
   9:  
  10: FeatureCollection features = new FeatureCollection(defaults);
  11: Debug.Assert(features.Revision == 2);
  12: Debug.Assert(features.Get<IFoo>().GetType() == typeof(Foo));
  13:  
  14: features.Set<IBaz>(new Baz());
  15: Debug.Assert(features.Revision == 3);

DefaultHttpContext

ASP.NET Core默認(rèn)使用的HttpContext類型為DefaultHttpContext,上面我們介紹的針對(duì)描述原始上下文“特性集合”來創(chuàng)建HttpContext的策略就體現(xiàn)在這個(gè)類型之上。DefaultHttpContext具有一個(gè)如下的構(gòu)造函數(shù),作為參數(shù)的FeatureCollection對(duì)象就是這么一個(gè)特性集合。

   1: public class DefaultHttpContext : HttpContext
   2: {
   3:     public DefaultHttpContext(IFeatureCollection features);
   4: }

不論是組成管道的中間件還是建立在管道上的應(yīng)用,在默認(rèn)的情況下都是利用這個(gè)DefaultHttpContext對(duì)象來獲取當(dāng)前請(qǐng)求的相關(guān)信息,并利用這個(gè)對(duì)象來控制最終發(fā)送的響應(yīng)。但是DefaultHttpContext對(duì)象這個(gè)這個(gè)過程中僅僅是一個(gè)“代理”,針對(duì)它的調(diào)用(屬性或者方法)最終都需要轉(zhuǎn)發(fā)給由具體服務(wù)器創(chuàng)建的那個(gè)原始上下文,在構(gòu)造函數(shù)中指定的這個(gè)FeatureCollection對(duì)象所代表的特性集合成為了這兩個(gè)上下文對(duì)象進(jìn)行溝通的唯一渠道。對(duì)于定義在DefaultHttpContext中的所有屬性,它們幾乎都具有一個(gè)對(duì)應(yīng)的特性,這些特性都對(duì)應(yīng)著一個(gè)接口。表1列出了部分特性接口以及DefaultHttpContext對(duì)應(yīng)的屬性。

表1 描述原始HTTP上下文的特性接口

接口

屬性

描述

IHttpRequestFeature

Request

獲取描述請(qǐng)求的基本信息。

IHttpResponseFeature

Response

控制對(duì)請(qǐng)求的響應(yīng)。

IHttpAuthenticationFeature

AuthenticationManger/User

提供完成用戶認(rèn)證的AuthenticationHandler對(duì)象和表示當(dāng)前用戶的ClaimsPrincipal對(duì)象

IHttpConnectionFeature

Connection

提供描述當(dāng)前HTTP連接的基本信息。

IItemsFeature

Items

提供用戶存放針對(duì)當(dāng)前請(qǐng)求的對(duì)象容器。

IHttpRequestLifetimeFeature

RequestAborted

傳遞請(qǐng)求處理取消通知和中止當(dāng)前請(qǐng)求處理。

IServiceProvidersFeature

RequestServices

提供根據(jù)服務(wù)注冊(cè)創(chuàng)建的ServiceProvider。

ISessionFeature

Session

提供描述當(dāng)前會(huì)話的Session對(duì)象。

IHttpRequestIdentifierFeature

TraceIdentifier

為追蹤日志(Trace)提供針對(duì)當(dāng)前請(qǐng)求的唯一標(biāo)識(shí)。

IHttpWebSocketFeature

WebSockets

管理WebSocket

對(duì)于上面列出的眾多特性接口,我們?cè)诤罄m(xù)相關(guān)章節(jié)中都會(huì)涉及到,目前來說我們只需要了解一下兩個(gè)最重要的特性接口,即表示請(qǐng)求和響應(yīng)的IHttpRequestFeature和IHttpResponseFeature。從下面給出的代碼片斷我們不難看出,這兩個(gè)接口的定義分別與抽象類HttpRequest和HttpResponse具有一致的定義。對(duì)于DefaultHttpContext類型來說,它的Request和Response屬性分別返回的是一個(gè)DefaultHttpRequest和DefaultHttpResponse對(duì)象。DefaultHttpRequest和DefaultHttpResponse分別繼承自HttpRequest和HttpResponse,它們分別利用這個(gè)兩個(gè)特性實(shí)現(xiàn)了從基類繼承下來的所有抽象成員。

   1: public interface IHttpRequestFeature
   2: {
   3:     Stream             Body { get; set; }
   4:     IHeaderDictionary  Headers { get; set; }
   5:     string             Method { get; set; }
   6:     string             Path { get; set; }
   7:     string             PathBase { get; set; }
   8:     string             Protocol { get; set; }
   9:     string             QueryString { get; set; }
  10:     string             Scheme { get; set; }
  11: }
  12:  
  13: public interface IHttpResponseFeature
  14: {
  15:     Stream             Body { get; set; }
  16:     bool               HasStarted { get; }
  17:     IHeaderDictionary  Headers { get; set; }
  18:     string             ReasonPhrase { get; set; }
  19:     int                 StatusCode { get; set; }
  20:  
  21:     void OnCompleted(Func<object, Task> callback, object state);
  22:     void OnStarting(Func<object, Task> callback, object state);
  23: }

對(duì)于實(shí)現(xiàn)請(qǐng)求監(jiān)聽、接收和響應(yīng)的服務(wù)器來說,它們都需要通過實(shí)現(xiàn)上面這些特性接口來定義針對(duì)性的特性類。如下圖所示,當(dāng)成功接收到請(qǐng)求之后,服務(wù)器會(huì)創(chuàng)建相應(yīng)的特性并將它們組合成一個(gè)FeatureCollection對(duì)象,最后創(chuàng)建出一個(gè)DefaultHttpContext對(duì)象,我們注冊(cè)的所有中間件針對(duì)這個(gè)DefaultHttpContext完成各自的請(qǐng)求處理工作。

HttpContextFactory

在服務(wù)器接收到抵達(dá)的請(qǐng)求時(shí),它并不會(huì)直接利用原始的上下文去創(chuàng)建HttpContext對(duì)象,HttpContext在管道中的創(chuàng)建是間接地通過HttpContextFactory來完成的。 HttpContextFactory是對(duì)所有實(shí)現(xiàn)了IHttpContextFactory接口的所有類型及其對(duì)象的統(tǒng)稱。如下面的代碼片段所示,IHttpContextFactory接口除了定義創(chuàng)建HttpContext對(duì)象的Create方法之外,還定義了另一個(gè)方法Dispose來釋放指定的HttpContext對(duì)象。HttpContextFactory類是該接口的默認(rèn)實(shí)現(xiàn)者,由它的Create方法創(chuàng)建并返回的自然是一個(gè)DefaultHttpContext對(duì)象。

   1: public interface IHttpContextFactory
   2: {
   3:     HttpContext Create(IFeatureCollection featureCollection);
   4:     void Dispose(HttpContext httpContext);
   5: }
   6:  
   7: public class HttpContextFactory : IHttpContextFactory
   8: {    
   9:     //省略其他成員
  10:     public HttpContext Create(IFeatureCollection featureCollection);
  11:     public void Dispose(HttpContext httpContext);
  12: }

綜上所述,組成管道的所有中間件在一個(gè)標(biāo)準(zhǔn)化的上下文中完整對(duì)請(qǐng)求的處理,這個(gè)上下文通過抽象類HttpContext表示,ASP.NET Core默認(rèn)使用的是它的子類DefaultHttpContext。一個(gè)DefaultHttpContext對(duì)象是根據(jù)描述原始上下文的特性集合,每個(gè)特性對(duì)應(yīng)的類型都實(shí)現(xiàn)了標(biāo)準(zhǔn)的接口,接口IHttpRequestFeature和IHttpResponseFeature分別代表針對(duì)請(qǐng)求和響應(yīng)的特性。HttpContext默認(rèn)情況下是通過注冊(cè)的工廠創(chuàng)建的,該工廠通過接口IHttpContextFactory表示,默認(rèn)使用的HttpContext工廠類型為HttpContextFactory,它也是DefaultHttpContext對(duì)象的創(chuàng)建者。

三、ApplicationBuilder

以類型為Func<RequestDelegate, RequestDelegate>的委托對(duì)象表示的中間件需要在啟動(dòng)的時(shí)候注冊(cè)到應(yīng)用程序上。所有注冊(cè)的中間件最終會(huì)轉(zhuǎn)換成一個(gè)RequestDelegate類型的委托對(duì)象,它們按照注冊(cè)順序?qū)φ?qǐng)求的處理流程最終體現(xiàn)在對(duì)這個(gè)委托對(duì)象的執(zhí)行。不論是最終將中間件轉(zhuǎn)換成RequestDelegate對(duì)象,還是最初對(duì)它們的注冊(cè),都是通過一個(gè)名為ApplicationBuilder的對(duì)象來完成的。

ApplicationBuilder是我們對(duì)所有實(shí)現(xiàn)了IApplicationBuilder接口的所有類型以及對(duì)應(yīng)對(duì)象的統(tǒng)稱。接口IApplicationBuilder定義如下,中間件的注冊(cè)和RequestDelegate對(duì)象的生成分別通過調(diào)用它的Use和Build方法來完成。除了這兩個(gè)核心方法,IApplicationBuilder接口還定義了三個(gè)屬性,其中ApplicationServices返回根據(jù)最初服務(wù)注冊(cè)生成的ServiceProvider對(duì)象,而ServerFeatures屬性返回的FeatureCollection對(duì)象是描述Server的特性集合。字典類型的Properties屬性用戶存儲(chǔ)任意自定義的屬性,而New方法會(huì)根據(jù)自己“克隆”出一個(gè)新的ApplicationBuilder對(duì)象,這兩個(gè)ApplicationBuilder對(duì)象應(yīng)用具有相同的屬性集合。

   1: public interface IApplicationBuilder
   2: {
   3:     IServiceProvider             ApplicationServices { get; set; }
   4:     IFeatureCollection           ServerFeatures { get; }
   5:     IDictionary<string, object>  Properties { get; }
   6:  
   7:     RequestDelegate         Build();
   8:     IApplicationBuilder     New();
   9:     IApplicationBuilder     Use(Func<RequestDelegate, RequestDelegate> middleware);
  10: }

具有如下定義的ApplicationBuilder類型是對(duì)IApplicationBuilder接口的默認(rèn)實(shí)現(xiàn)。ApplicationBuilder類型利用一個(gè)List<Func<RequestDelegate, RequestDelegate>>對(duì)象來保存注冊(cè)的中間件,所以Use方法只需要將指定的中間件添加到這個(gè)列表中即可,而Build方法只需要逆序調(diào)用這些注冊(cè)的中間件對(duì)應(yīng)的Func<RequestDelegate, RequestDelegate>對(duì)象就能得到我們需要的RequestDelegate對(duì)象。值得一提的是,Build方法實(shí)際上在中間件鏈條的尾部添加了一個(gè)額外的中間件,該中間件會(huì)負(fù)責(zé)將響應(yīng)狀態(tài)碼設(shè)置為404,如果我們沒有注冊(cè)一個(gè)中間件對(duì)請(qǐng)求作最終的響應(yīng)(這樣的中間件將不會(huì)試圖調(diào)用后續(xù)中間件),整個(gè)管道比較回復(fù)一個(gè)狀態(tài)碼為404的響應(yīng)。

   1: public class ApplicationBuilder : IApplicationBuilder
   2: {
   3:     private readonly IList<Func<RequestDelegate, RequestDelegate>> middlewares = new List<Func<RequestDelegate, RequestDelegate>>();
   4:  
   5:     public IDictionary<string, object> Properties { get; }
   6:  
   7:     public IServiceProvider ApplicationServices
   8:     {
   9:         get { return GetProperty<IServiceProvider>("application.Services"); }
  10:         set { SetProperty<IServiceProvider>("application.Services", value); }
  11:     }
  12:  
  13:     public IFeatureCollection ServerFeatures
  14:     {
  15:         get { return GetProperty<IFeatureCollection>("server.Features"); }
  16:     }
  17:  
  18:  
  19:     public ApplicationBuilder(IServiceProvider serviceProvider)
  20:     {
  21:         this.Properties = new Dictionary<string, object>();
  22:         ApplicationServices = serviceProvider;
  23:     }
  24:  
  25:     public ApplicationBuilder(IServiceProvider serviceProvider, object server)
  26:         : this(serviceProvider)
  27:     {
  28:         SetProperty("server.Features", server);
  29:     }
  30:  
  31:     public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
  32:     {
  33:         middlewares.Add(middleware);
  34:         return this;
  35:     }
  36:  
  37:     public IApplicationBuilder New()
  38:     {
  39:         return new ApplicationBuilder(this);
  40:     }
  41:  
  42:     public RequestDelegate Build()
  43:     {
  44:         RequestDelegate app = context =>
  45:         {
  46:             context.Response.StatusCode = 404;
  47:             return Task.FromResult(0);
  48:         };
  49:         foreach (var component in middlewares.Reverse())
  50:         {
  51:             app = component(app);
  52:         }
  53:         return app;
  54: }
  55:  
  56:     private ApplicationBuilder(ApplicationBuilder builder)
  57:     {
  58:         this.Properties = builder.Properties;
  59:     }        
  60:  
  61:     private T GetProperty<T>(string key)
  62:     {
  63:         object value;
  64:         return Properties.TryGetValue(key, out value) ? (T)value : default(T);
  65:     }
  66:  
  67:     private void SetProperty<T>(string key, T value)
  68:     {
  69:         this.Properties[key] = value;
  70:     }
  71: }

通過上面的代碼片段我們不難看到,不論是通過ApplicationServices屬性返回的ServiceProvider對(duì)象,還是通過ServerFeatures屬性返回的用于描述Server特性的FeatureCollection對(duì)象,它們實(shí)際上都保存在通過Properties屬性返回字典對(duì)象上。ApplicationBuilder具有兩個(gè)公共構(gòu)造函數(shù)重載,它們具有一個(gè)公共的參數(shù),即用來初始化ApplicationServices屬性的參數(shù)serviceProvider。

一個(gè)構(gòu)造函數(shù)具有一個(gè)名為server的參數(shù),但是這個(gè)參數(shù)并不是表示管道使用的服務(wù)器,而是承載服務(wù)器相關(guān)特性的FeatureCollection對(duì)象,不過這個(gè)參數(shù)類型被定義成Object,而不是IFeatureCollection接口。New方法直接調(diào)用私有構(gòu)造函數(shù)創(chuàng)建出一個(gè)新的ApplicationBuilder對(duì)象,這個(gè)對(duì)象與自己的Properties屬性共享同一個(gè)字典對(duì)象,由于ApplicationServices和ServerFeatures屬性的返回值也存放在這個(gè)字典對(duì)象上,所以New方法得到的ApplicationBuilder對(duì)象與自身對(duì)象其實(shí)是完全等效的。

ApplicationBuilderFactory

ApplicationBuilderFactory是ASP.NET Core它用來創(chuàng)建ApplicationBuilder的工廠,它是對(duì)所有實(shí)現(xiàn)了接口IApplicationBuilderFactory的所有類型以及對(duì)應(yīng)對(duì)象的統(tǒng)稱。如下面的代碼片段所示,該接口定義了唯一個(gè)方法CreateBuilder根據(jù)提供的FeatureCollection對(duì)象創(chuàng)建出對(duì)應(yīng)的ApplicationBuilder對(duì)象,這個(gè)FeatureCollection對(duì)象正是承載與服務(wù)器相關(guān)特性的集合。ApplicationBuilderFactory類型是該接口的默認(rèn)實(shí)現(xiàn)者,當(dāng)CreateBuilder方法被調(diào)用的時(shí)候,它會(huì)直接將構(gòu)造時(shí)提供ServiceProvider對(duì)象和serverFeatures參數(shù)表示的FeatureCollection對(duì)象來創(chuàng)建返回的ApplicationBuilder對(duì)象。

   1: public interface IApplicationBuilderFactory
   2: {
   3:     IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures);
   4: }
   5:  
   6: public class ApplicationBuilderFactory : IApplicationBuilderFactory
   7: {
   8:     private readonly IServiceProvider _serviceProvider;
   9:  
  10:     public ApplicationBuilderFactory(IServiceProvider serviceProvider)
  11:     {
  12:         this._serviceProvider = serviceProvider;
  13:     }
  14:  
  15:     public IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures)
  16:     {
  17:         return new ApplicationBuilder(_serviceProvider, serverFeatures);
  18:     }
  19: }

中間件類型

雖然中間件最終體現(xiàn)為一個(gè)類型為 Func<RequestDelegate, RequestDelegate>的委托對(duì)象,但是我們?cè)诖蟛糠智闆r下都會(huì)將中間件定義成一個(gè)單獨(dú)的類型。雖然這樣的中間件類型不要求實(shí)現(xiàn)某個(gè)預(yù)定義的接口或者繼承某個(gè)預(yù)定義的基類,但是卻要遵守幾個(gè)必要的約定。接下來我們直接如下這個(gè)ContentMiddleware類說說一個(gè)合法的中間件類型應(yīng)該如何定義。

   1: public class ContentMiddleare
   2: {
   3:     public RequestDelegate     _next;
   4:     public byte[]         _content;
   5:     public string         _contentType;
   6:  
   7:     public ContentMiddleare(RequestDelegate next, byte[] content, string contentType)
   8:     {
   9:         _next         = next;
  10:         _content      = content;
  11:         _contentType  = contentType;
  12:     }
  13:  
  14:     public async Task Invoke(HttpContext context, ILoggerFactory loggerFactory)
  15:     {
  16:         loggerFactory.CreateLogger<ContentMiddleare>().LogInformation($"Write content ({_contentType})");
  17:         context.Response.ContentType = _contentType;
  18:         await context.Response.Body.WriteAsync(_content,0, _content.Length);
  19:     }
  20: }

如上所示的這個(gè)中間件(ContentMiddleware)可以幫助我們將任何類型的內(nèi)容響應(yīng)給客戶端,它的兩個(gè)字段_content和_contentType分別代表響應(yīng)內(nèi)容和媒體類型(內(nèi)容類型或者M(jìn)IME類型),它體現(xiàn)了一個(gè)典型中間件類型的定義規(guī)則或者約定:

  • 應(yīng)該定義成實(shí)例類,不能定義成靜態(tài)類。
  • 具有一個(gè)有效的公共構(gòu)造函數(shù)。這個(gè)構(gòu)造函數(shù)的第一個(gè)參數(shù)類型必須為RequestDelegate,代表對(duì)請(qǐng)求的后續(xù)操作(可以視為下一個(gè)注冊(cè)的中間件),至于后續(xù)參數(shù)的個(gè)數(shù)和類型則不作要求。
  • 針對(duì)請(qǐng)求的處理定義在一個(gè)名為Invoke的公共實(shí)例方法,其返回類型為Task。該方法的第一個(gè)參數(shù)類型為HttpContext,代表當(dāng)前HTTP上下文。我們可以為這個(gè)方法定義任意數(shù)量和類型的后續(xù)參數(shù),當(dāng)這個(gè)方法被執(zhí)行的時(shí)候,系統(tǒng)將會(huì)采用依賴注入的方式提供響應(yīng)的服務(wù)來為這個(gè)參數(shù)賦值。

中間件類型的注冊(cè)

中間件類型的注冊(cè)可以通過調(diào)用 IApplicationBuilder接口的擴(kuò)展方法UseMiddleware 和UseMiddleware
< TMiddleware >進(jìn)行注冊(cè)。如下面的代碼片斷所示,除了指定中間件的類型之外,我們還需要按照順序指定調(diào)用目標(biāo)構(gòu)造函數(shù)的全部或者部分參數(shù)。不過不過的參數(shù)列表不需要提供作為第一個(gè)參數(shù)的RequestDelegate,如果僅僅指定了部分參數(shù),缺失的參數(shù)將會(huì)自動(dòng)通過ServiceProvider來提供。

   1: public static class UseMiddlewareExtensions
   2: {
   3:     public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args);
   4:     public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args);
   5: }

對(duì)于上面定義的這個(gè) ContentMiddleare類型,我們按照如下的方式對(duì)它進(jìn)行了注冊(cè)。當(dāng)這個(gè)中間件執(zhí)行的時(shí)候,它會(huì)響應(yīng)客戶端一張PNG圖片。如果客戶端是能夠支持圖片呈現(xiàn)的瀏覽器,這張圖片會(huì)直接顯示在瀏覽器上。

   1: new WebHostBuilder()
   2:     .Configure(app=>app.UseMiddleware<ContentMiddleare>(File.ReadAllBytes("girl.png"),"image/png"))
   3: ...

雖然中間件可以定義成任何一個(gè)遵循約定的類型,但是中間件自身在ASP.NET Core框架中總是體現(xiàn)為一個(gè)類型為Func<RequestDelegate, RequestDelegate>的委托對(duì)象,所以上述的這個(gè)UseMiddleware方法在執(zhí)行的時(shí)候需要在內(nèi)部根據(jù)注冊(cè)的中間件類型和指定的參數(shù)列表創(chuàng)建這么一個(gè)Func<RequestDelegate, RequestDelegate>對(duì)象。其中的邏輯并不復(fù)雜,它之需要將中間件對(duì)象的創(chuàng)建和針對(duì)Invoke方法的調(diào)用實(shí)現(xiàn)在返回的委托對(duì)象中就可以了。值得一提的是,針對(duì)Invoke方法的調(diào)用并沒有直接通過反射的方式來實(shí)現(xiàn),而是采用表達(dá)式,后者具有更好的性能。在如下所示的代碼片段中,我采用最精簡(jiǎn)的代碼模擬了UseMiddleware方法的實(shí)現(xiàn)。

   1: public static class WebHostBuilderExtensions
   2: {
   3:     private static MethodInfo GetServiceMethod = typeof(WebHostBuilderExtensions).GetMethod("GetService", BindingFlags.Static | BindingFlags.NonPublic);
   4:  
   5:     public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args)
   6:     {
   7:         return UseMiddleware2(app, typeof(TMiddleware), args);
   8:     }
   9:  
  10:     public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middlewareType, params object[] args)
  11:     {
  12:         return app.Use(next =>
  13:         {
  14:             return context => {
  15:                 var factory = Compile<object>(middlewareType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public));
  16:                 object middleware = CreateMiddleware(app, middlewareType, next, args);
  17:                 return factory(middleware, context, app.ApplicationServices);
  18:             };              
  19:         });
  20:     }
  21:  
  22:     private static object CreateMiddleware(IApplicationBuilder app, Type middlewareType, RequestDelegate next, params object[] args)
  23:     {
  24:         object[] arguments = new object[args.Length + 1];
  25:         arguments[0] = next;
  26:         args.CopyTo(arguments, 1);
  27:         return ActivatorUtilities.CreateInstance(app.ApplicationServices, middlewareType, arguments);
  28:     }
  29:     
  30:     //將對(duì)Invoke方法的調(diào)用轉(zhuǎn)換成一個(gè)Func<TMiddleware, HttpContext, IServiceProvider, Task>對(duì)象
  31:     private static Func<TMiddleware, HttpContext, IServiceProvider, Task> Compile<TMiddleware>(MethodInfo invokeMethod)
  32:     {           
  33:         ParameterExpression middleware = Expression.Parameter(typeof(TMiddleware), "middleware");
  34:         ParameterExpression httpContext = Expression.Parameter(typeof(HttpContext), "httpContext");
  35:         ParameterExpression serviceProvider = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
  36:  
  37:         var arguments = from parameter in invokeMethod.GetParameters()
  38:             select GetArgument(httpContext, serviceProvider, parameter.ParameterType);
  39:  
  40:         Expression instance = middleware;
  41:         if (invokeMethod.DeclaringType != typeof(TMiddleware))
  42:         {
  43:             instance = Expression.Convert(instance, invokeMethod.DeclaringType);
  44:         }
  45:  
  46:         Expression invoke = Expression.Call(instance, invokeMethod, arguments.ToArray());
  47:         return Expression.Lambda<Func<TMiddleware, HttpContext, IServiceProvider, Task>>(invoke, middleware, httpContext, serviceProvider).Compile();
  48:     }
  49:     
  50:     //生成調(diào)用Invoke方法的參數(shù)表達(dá)式
  51:     private static Expression GetArgument(Expression httpContext, Expression serviceProvider, Type parameterType)
  52:     {
  53:         if (parameterType == typeof(HttpContext))
  54:         {
  55:             return httpContext;
  56:         }
  57:         Expression serviceType = Expression.Constant(parameterType, typeof(Type));
  58:         Expression callGetService = Expression.Call(GetServiceMethod, serviceProvider, serviceType);
  59:         return Expression.Convert(callGetService, parameterType);
  60:     }
  61:  
  62:     private static object GetService(IServiceProvider serviceProvider, Type serviceType)
  63:     {
  64:         return serviceProvider.GetService(serviceType);
  65:     }
  66: }

?轉(zhuǎn)自https://www.cnblogs.com/artech/p/asp-net-core-real-pipeline-01.html


該文章在 2025/1/16 10:49:25 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved