學(xué)習(xí)ASP.NET Core,怎能不了解請求處理管道:中間件究竟是個什么東西?
當(dāng)前位置:點(diǎn)晴教程→知識管理交流
→『 技術(shù)文檔交流 』
ASP.NET Core管道雖然在結(jié)構(gòu)組成上顯得非常簡單,但是在具體實(shí)現(xiàn)上卻涉及到太多的對象,所以我們在 “通過重建Hosting系統(tǒng)理解HTTP請求在ASP.NET Core管道中的處理流程”(上篇、中篇、下篇) 中圍繞著一個經(jīng)過極度簡化的模擬管道講述了真實(shí)管道構(gòu)建的方式以及處理HTTP請求的流程。在本系列 中,我們會還原構(gòu)建模擬管道時可以舍棄和改寫的部分,向讀者朋友們呈現(xiàn)一個真是的HTTP請求處理管道。 ASP.NET Core 的請求處理管道由一個服務(wù)器與一組有序排列的中間件構(gòu)成,前者僅僅完成請求監(jiān)聽、接收和響應(yīng)這些與底層網(wǎng)絡(luò)相關(guān)的工作,至于請求接收之后和響應(yīng)之前的所有工作都交給中間件來完成。ASP.NET Core的中間件通過一個類型Func<RequestDelegate, RequestDelegate>的委托對象來表示,而RequestDelegate也是一個委托,它代表一項(xiàng)請求處理任務(wù)。 [本文已經(jīng)同步到《ASP.NET Core框架揭秘》之中]
一、RequestDelegate服務(wù)器接受到抵達(dá)的HTTP請求之后會構(gòu)建一個描述當(dāng)前請求的原始上下文,服務(wù)器的類型決定了這個原始上下文的類型,比如在我們模擬管道默認(rèn)采用的HttpListenerServer由于采用HttpListener來監(jiān)聽、接收并響應(yīng)請求,所以它對應(yīng)的原始上下文是一個HttpListenerContext對象。但是對于管道的后續(xù)部分,即由注冊的中間件構(gòu)建的鏈表,它們需要采用統(tǒng)一的方式來處理請求,所以服務(wù)器最終會根據(jù)原始的上下文來創(chuàng)建一個抽象的HTTP上下文,后者通過抽象類HttpContext來表示。 我們不僅可以利用這個HttpContext獲取描述當(dāng)前請求的上下文信息,同樣可以利用它來實(shí)現(xiàn)對響應(yīng)的控制。針對當(dāng)前請求的任何處理操作總是在這么一個上下文中進(jìn)行,所以一項(xiàng)請求處理任務(wù)完全可以抽象成一個類型Func<HttpContext,Task>的委托來表示,實(shí)際上具有如下定義的RequestDelegate委托具有類似的定義。 1: public delegate Task RequestDelegate(HttpContext context); 每個中間件都承載著獨(dú)立的請求處理任務(wù),它本質(zhì)上也體現(xiàn)了在當(dāng)前HttpContext下針對請求的處理操作,那么為什么中間件不直接通過一個RequestDelegate對象來表示,而是表示為一個類型為Func<RequestDelegate, RequestDelegate>的委托對象呢?原因很簡單,中間件并不孤立地存在,所有注冊的中間件最終會根據(jù)注冊的先后順序組成一個鏈表,每個中間件不僅僅需要完成各自的請求處理任務(wù)外,還需要驅(qū)動鏈表中的下一個中間件。 如上圖所示,對于一個由多個Func<RequestDelegate, RequestDelegate>對象組成的中間鏈表來說,某個中間件會將后一個Func<RequestDelegate, RequestDelegate>對象的返回值作為輸入,而自身的返回值則作為前一個中間件的輸入。某個中間件執(zhí)行之后返回的RequestDelegate對象不僅僅體現(xiàn)了自身對請求的處理操作,而是體現(xiàn)了包含自己和后續(xù)中間件一次對請求的處理。那么對于第一個中間件來說,它執(zhí)行后返回的RequestDelegate對象實(shí)際上體現(xiàn)了整個應(yīng)用對請求的處理邏輯。 二、 HttpContext對當(dāng)前上下文的抽象解除了管道對具體服務(wù)器類型的依賴, 這使我們可以為ASP.NET Core應(yīng)用自由地選擇承載(Hosting)方式,而不是像傳統(tǒng)的ASP.NET應(yīng)用一樣只能寄宿在IIS之中。抽象HTTP上下文的目的是為了實(shí)現(xiàn)對請求處理流程的抽象,只有這樣我們才能將針對請求的某項(xiàng)操作體現(xiàn)在一個標(biāo)準(zhǔn)的中間件上,有了這個這個標(biāo)準(zhǔn)化的中間件才有所謂的請求處理管道。 ASP.NET Core通過具有如下所示的HttpContext類來表示這么一個抽象的HTTP上下文。對于一個HttpContext對象來說,它的核心體現(xiàn)在用于描述請求和響應(yīng)的Request和Response屬性之上。除此之外,我們還可以通過它獲取與當(dāng)前請求相關(guān)的其他上下文信息,比如用來控制用戶認(rèn)證的AuthenticationManager對象和代表當(dāng)前請求用戶的ClaimsPrincipal對象,以及描述當(dāng)前HTTP連接的ConnectionInfo對象和用于控制WebSocket的WebSocketManager。我們可以獲取并控制當(dāng)前會話,也可以獲取或者設(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)需要中指對請求的處理時,我們可以通過為RequestAborted屬性設(shè)置一個CancellationToken對象從而將終止通知發(fā)送給管道。如果需要對整個管道共享一些與當(dāng)前上下文相關(guān)的數(shù)據(jù),我們可以將它保存在通過Items屬性表示的字典中。我們一再提到依賴注入被廣泛地應(yīng)用ASP.NET Core管道中,HttpContext的RequestServices屬性返回的根據(jù)在應(yīng)用啟動時注冊的服務(wù)而創(chuàng)建的ServiceProvider。只要相應(yīng)的服務(wù)被預(yù)先注冊到指定的服務(wù)接口上,我們就可能利用這個ServiceProvider根據(jù)這個接口得到對應(yīng)的服務(wù)對象。 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是對HTTP請求的描述,它是HttpContext的只讀屬性Request的返回類型。我們可以利用這個對象獲取到描述當(dāng)前請求的各種相關(guān)信息,比如請求的協(xié)議(HTTP或者HTTPS)、HTTP方法、地址,也可以獲取代表請求的HTTP消息的首部和主體。 在了解了表示請求的抽象類HttpRequest之后,我們再來認(rèn)識一個與之相對的用于描述響應(yīng)HttpResponse類型。如下面的代碼片斷所示,HttpResponse依然是一個抽象類,我們可以通過定義在它之上的屬性和方法來控制對請求的響應(yīng)。從原則上講,我們對請求的所做的任意類型的響應(yīng)都可以利用它來說實(shí)現(xiàn)。當(dāng)我們通過表示當(dāng)前上下文的HttpContext對象得到表示響應(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: } FeatureCollectionHttpContext的另一個只讀屬性Features返回一組“特性”對象。在ASP.NET Core管道式處理設(shè)計中,特性是一個非常重要的概念,特性是實(shí)現(xiàn)抽象化HttpContext的途徑。具體來說,服務(wù)器在接收到請求之后會創(chuàng)建一個由自身類型決定的原始的上下文,管道不僅僅利用這個原始上下文來獲取與請求相關(guān)的信息,它對請求的最終響應(yīng)實(shí)際上也是通過這個原始上下文來完成的。所以對一個HttpContext對象來說,由它描述的上下文信息不僅僅來源于這個原始的上下文,我們針對HttpContext所做的任何響應(yīng)操作最終都需要分發(fā)給這個原始上下文來完成, 否則是不會生效的。抽象的HttpContext和原始上下文之間的“雙向綁定”究竟是如何實(shí)現(xiàn)的呢? 這個所謂的“雙向綁定”即使其實(shí)很簡單。當(dāng)原始上下文被創(chuàng)建出來之后,服務(wù)器會將它封裝成一系列標(biāo)準(zhǔn)的特性對象,HttpContext正是對這些特性對象的封裝。一般來說,這些特性對象所對應(yīng)的類型均實(shí)現(xiàn)了某個預(yù)定義的標(biāo)準(zhǔn)接口,接口中不僅僅定義相應(yīng)的屬性來讀寫原始上下文中描述的信息,還定義了相應(yīng)的方法來操作原始上下文。HttpContext的屬性Features返回的就是這組特性對象的集合,它的返回類型為IFeatureCollection,我們將實(shí)現(xiàn)了該接口的類型以及對應(yīng)的對象統(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; } 一個FeatureCollection對象本質(zhì)上就是一個Key和Value分別為Type和Object類型的字段,話句話說,特性對象通過對應(yīng)的接口類型注冊到HttpContext之上。我們通過調(diào)用Set方法將一個特性對象針對指定的類型(一般為特性接口)注冊到這個字典對象上,并通過Get方法根據(jù)注冊的類型獲取它。特性對象的注冊和獲取也可以利用定義的索引來完成。如果IsReadOnly屬性返回True,我們將不能注冊新的特性或者修改已經(jīng)注冊的特性。 整數(shù)類型的之都屬性Revision可以視為整個FeatureCollection對象的版本,不論是采用何種方式注冊新的特性還是修改現(xiàn)有的特性,這個屬性的值都將改變。 具有如下定義的FeatureCollection類實(shí)現(xiàn)了IFeatureCollection接口,我們默認(rèn)使用的FeatureCollection就是這么一個類型的對象。FeatureCollection具有兩個構(gòu)造函數(shù)重載,默認(rèn)無參構(gòu)造函數(shù)幫助我們創(chuàng)建一個空的特性集合,另一個構(gòu)造函數(shù)則需要指定一個FeatureCollection對象來提供默認(rèn)特性。對于采用第二個構(gòu)造函數(shù)重載創(chuàng)建的 FeatureCollection對象來說,當(dāng)我們通過指定某個特性接口類型試圖獲取對應(yīng)的特性對象時,如果對應(yīng)的特性沒有注冊到當(dāng)前FeatureCollection對象上,而是注冊到提供默認(rèn)特性的FeatureCollection對象上,后者將會提供最終的特性。 1: public class FeatureCollection : IFeatureCollection 2: { 3: //其他成員 4: public FeatureCollection(); 5: public FeatureCollection(IFeatureCollection defaults); 6: } 對于FeatureCollection類型來說,它 的IsReadOnly總是返回False,所以它永遠(yuǎn)是可讀可寫的。對于調(diào)用默認(rèn)無參構(gòu)造函數(shù)創(chuàng)建的FeatureCollection對象來說,它 的Revision默認(rèn)返回零。如果我們通過指定另一個FeatureCollection對象為參數(shù)調(diào)用第二個構(gòu)造函數(shù)來創(chuàng)建一個FeatureCollection對象,前者的Revision屬性值將成為后者同名屬性的默認(rèn)值。不論我們采用何種形式(調(diào)用Set方法或者索引)添加一個新的特性或者改變了一個已經(jīng)注冊的特性,F(xiàn)eatureCollection對象的Revision屬性都將自動遞增。上述的這些關(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); DefaultHttpContextASP.NET Core默認(rèn)使用的HttpContext類型為DefaultHttpContext,上面我們介紹的針對描述原始上下文“特性集合”來創(chuàng)建HttpContext的策略就體現(xiàn)在這個類型之上。DefaultHttpContext具有一個如下的構(gòu)造函數(shù),作為參數(shù)的FeatureCollection對象就是這么一個特性集合。 1: public class DefaultHttpContext : HttpContext 2: { 3: public DefaultHttpContext(IFeatureCollection features); 4: } 不論是組成管道的中間件還是建立在管道上的應(yīng)用,在默認(rèn)的情況下都是利用這個DefaultHttpContext對象來獲取當(dāng)前請求的相關(guān)信息,并利用這個對象來控制最終發(fā)送的響應(yīng)。但是DefaultHttpContext對象這個這個過程中僅僅是一個“代理”,針對它的調(diào)用(屬性或者方法)最終都需要轉(zhuǎn)發(fā)給由具體服務(wù)器創(chuàng)建的那個原始上下文,在構(gòu)造函數(shù)中指定的這個FeatureCollection對象所代表的特性集合成為了這兩個上下文對象進(jìn)行溝通的唯一渠道。對于定義在DefaultHttpContext中的所有屬性,它們幾乎都具有一個對應(yīng)的特性,這些特性都對應(yīng)著一個接口。表1列出了部分特性接口以及DefaultHttpContext對應(yīng)的屬性。 表1 描述原始HTTP上下文的特性接口
對于上面列出的眾多特性接口,我們在后續(xù)相關(guān)章節(jié)中都會涉及到,目前來說我們只需要了解一下兩個最重要的特性接口,即表示請求和響應(yīng)的IHttpRequestFeature和IHttpResponseFeature。從下面給出的代碼片斷我們不難看出,這兩個接口的定義分別與抽象類HttpRequest和HttpResponse具有一致的定義。對于DefaultHttpContext類型來說,它的Request和Response屬性分別返回的是一個DefaultHttpRequest和DefaultHttpResponse對象。DefaultHttpRequest和DefaultHttpResponse分別繼承自HttpRequest和HttpResponse,它們分別利用這個兩個特性實(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: } 對于實(shí)現(xiàn)請求監(jiān)聽、接收和響應(yīng)的服務(wù)器來說,它們都需要通過實(shí)現(xiàn)上面這些特性接口來定義針對性的特性類。如下圖所示,當(dāng)成功接收到請求之后,服務(wù)器會創(chuàng)建相應(yīng)的特性并將它們組合成一個FeatureCollection對象,最后創(chuàng)建出一個DefaultHttpContext對象,我們注冊的所有中間件針對這個DefaultHttpContext完成各自的請求處理工作。 HttpContextFactory在服務(wù)器接收到抵達(dá)的請求時,它并不會直接利用原始的上下文去創(chuàng)建HttpContext對象,HttpContext在管道中的創(chuàng)建是間接地通過HttpContextFactory來完成的。 HttpContextFactory是對所有實(shí)現(xiàn)了IHttpContextFactory接口的所有類型及其對象的統(tǒng)稱。如下面的代碼片段所示,IHttpContextFactory接口除了定義創(chuàng)建HttpContext對象的Create方法之外,還定義了另一個方法Dispose來釋放指定的HttpContext對象。HttpContextFactory類是該接口的默認(rèn)實(shí)現(xiàn)者,由它的Create方法創(chuàng)建并返回的自然是一個DefaultHttpContext對象。 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: } 綜上所述,組成管道的所有中間件在一個標(biāo)準(zhǔn)化的上下文中完整對請求的處理,這個上下文通過抽象類HttpContext表示,ASP.NET Core默認(rèn)使用的是它的子類DefaultHttpContext。一個DefaultHttpContext對象是根據(jù)描述原始上下文的特性集合,每個特性對應(yīng)的類型都實(shí)現(xiàn)了標(biāo)準(zhǔn)的接口,接口IHttpRequestFeature和IHttpResponseFeature分別代表針對請求和響應(yīng)的特性。HttpContext默認(rèn)情況下是通過注冊的工廠創(chuàng)建的,該工廠通過接口IHttpContextFactory表示,默認(rèn)使用的HttpContext工廠類型為HttpContextFactory,它也是DefaultHttpContext對象的創(chuàng)建者。 三、ApplicationBuilder以類型為Func<RequestDelegate, RequestDelegate>的委托對象表示的中間件需要在啟動的時候注冊到應(yīng)用程序上。所有注冊的中間件最終會轉(zhuǎn)換成一個RequestDelegate類型的委托對象,它們按照注冊順序?qū)φ埱蟮奶幚砹鞒套罱K體現(xiàn)在對這個委托對象的執(zhí)行。不論是最終將中間件轉(zhuǎn)換成RequestDelegate對象,還是最初對它們的注冊,都是通過一個名為ApplicationBuilder的對象來完成的。 ApplicationBuilder是我們對所有實(shí)現(xiàn)了IApplicationBuilder接口的所有類型以及對應(yīng)對象的統(tǒng)稱。接口IApplicationBuilder定義如下,中間件的注冊和RequestDelegate對象的生成分別通過調(diào)用它的Use和Build方法來完成。除了這兩個核心方法,IApplicationBuilder接口還定義了三個屬性,其中ApplicationServices返回根據(jù)最初服務(wù)注冊生成的ServiceProvider對象,而ServerFeatures屬性返回的FeatureCollection對象是描述Server的特性集合。字典類型的Properties屬性用戶存儲任意自定義的屬性,而New方法會根據(jù)自己“克隆”出一個新的ApplicationBuilder對象,這兩個ApplicationBuilder對象應(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類型是對IApplicationBuilder接口的默認(rèn)實(shí)現(xiàn)。ApplicationBuilder類型利用一個List<Func<RequestDelegate, RequestDelegate>>對象來保存注冊的中間件,所以Use方法只需要將指定的中間件添加到這個列表中即可,而Build方法只需要逆序調(diào)用這些注冊的中間件對應(yīng)的Func<RequestDelegate, RequestDelegate>對象就能得到我們需要的RequestDelegate對象。值得一提的是,Build方法實(shí)際上在中間件鏈條的尾部添加了一個額外的中間件,該中間件會負(fù)責(zé)將響應(yīng)狀態(tài)碼設(shè)置為404,如果我們沒有注冊一個中間件對請求作最終的響應(yīng)(這樣的中間件將不會試圖調(diào)用后續(xù)中間件),整個管道比較回復(fù)一個狀態(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對象,還是通過ServerFeatures屬性返回的用于描述Server特性的FeatureCollection對象,它們實(shí)際上都保存在通過Properties屬性返回字典對象上。ApplicationBuilder具有兩個公共構(gòu)造函數(shù)重載,它們具有一個公共的參數(shù),即用來初始化ApplicationServices屬性的參數(shù)serviceProvider。 一個構(gòu)造函數(shù)具有一個名為server的參數(shù),但是這個參數(shù)并不是表示管道使用的服務(wù)器,而是承載服務(wù)器相關(guān)特性的FeatureCollection對象,不過這個參數(shù)類型被定義成Object,而不是IFeatureCollection接口。New方法直接調(diào)用私有構(gòu)造函數(shù)創(chuàng)建出一個新的ApplicationBuilder對象,這個對象與自己的Properties屬性共享同一個字典對象,由于ApplicationServices和ServerFeatures屬性的返回值也存放在這個字典對象上,所以New方法得到的ApplicationBuilder對象與自身對象其實(shí)是完全等效的。 ApplicationBuilderFactoryApplicationBuilderFactory是ASP.NET Core它用來創(chuàng)建ApplicationBuilder的工廠,它是對所有實(shí)現(xiàn)了接口IApplicationBuilderFactory的所有類型以及對應(yīng)對象的統(tǒng)稱。如下面的代碼片段所示,該接口定義了唯一個方法CreateBuilder根據(jù)提供的FeatureCollection對象創(chuàng)建出對應(yīng)的ApplicationBuilder對象,這個FeatureCollection對象正是承載與服務(wù)器相關(guān)特性的集合。ApplicationBuilderFactory類型是該接口的默認(rèn)實(shí)現(xiàn)者,當(dāng)CreateBuilder方法被調(diào)用的時候,它會直接將構(gòu)造時提供ServiceProvider對象和serverFeatures參數(shù)表示的FeatureCollection對象來創(chuàng)建返回的ApplicationBuilder對象。 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)為一個類型為 Func<RequestDelegate, RequestDelegate>的委托對象,但是我們在大部分情況下都會將中間件定義成一個單獨(dú)的類型。雖然這樣的中間件類型不要求實(shí)現(xiàn)某個預(yù)定義的接口或者繼承某個預(yù)定義的基類,但是卻要遵守幾個必要的約定。接下來我們直接如下這個ContentMiddleware類說說一個合法的中間件類型應(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: } 如上所示的這個中間件(ContentMiddleware)可以幫助我們將任何類型的內(nèi)容響應(yīng)給客戶端,它的兩個字段_content和_contentType分別代表響應(yīng)內(nèi)容和媒體類型(內(nèi)容類型或者M(jìn)IME類型),它體現(xiàn)了一個典型中間件類型的定義規(guī)則或者約定:
中間件類型的注冊中間件類型的注冊可以通過調(diào)用 IApplicationBuilder接口的擴(kuò)展方法UseMiddleware 和UseMiddleware 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: } 對于上面定義的這個 ContentMiddleare類型,我們按照如下的方式對它進(jìn)行了注冊。當(dāng)這個中間件執(zhí)行的時候,它會響應(yīng)客戶端一張PNG圖片。如果客戶端是能夠支持圖片呈現(xiàn)的瀏覽器,這張圖片會直接顯示在瀏覽器上。 1: new WebHostBuilder() 2: .Configure(app=>app.UseMiddleware<ContentMiddleare>(File.ReadAllBytes("girl.png"),"image/png")) 3: ... 雖然中間件可以定義成任何一個遵循約定的類型,但是中間件自身在ASP.NET Core框架中總是體現(xiàn)為一個類型為Func<RequestDelegate, RequestDelegate>的委托對象,所以上述的這個UseMiddleware方法在執(zhí)行的時候需要在內(nèi)部根據(jù)注冊的中間件類型和指定的參數(shù)列表創(chuàng)建這么一個Func<RequestDelegate, RequestDelegate>對象。其中的邏輯并不復(fù)雜,它之需要將中間件對象的創(chuàng)建和針對Invoke方法的調(diào)用實(shí)現(xiàn)在返回的委托對象中就可以了。值得一提的是,針對Invoke方法的調(diào)用并沒有直接通過反射的方式來實(shí)現(xiàn),而是采用表達(dá)式,后者具有更好的性能。在如下所示的代碼片段中,我采用最精簡的代碼模擬了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: //將對Invoke方法的調(diào)用轉(zhuǎn)換成一個Func<TMiddleware, HttpContext, IServiceProvider, Task>對象 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)文章
正在查詢... |