200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]
2019年1月19日,微軟技術(shù)(蘇州)俱樂(lè)部成立,我受邀在成立大會(huì)上作了一個(gè)名為《ASP.NET Core框架揭秘》的分享。在此次分享中,我按照ASP.NET Core自身的運(yùn)行原理和設(shè)計(jì)思想創(chuàng)建了一個(gè) “迷你版” 的ASP.NET Core框架,并且利用這個(gè) “極簡(jiǎn)” 的模擬框架闡述了ASP.NET Core框架最核心、最本質(zhì)的東西。整個(gè)框架涉及到的核心代碼不會(huì)超過(guò)200行,涉及到7個(gè)核心的對(duì)象。由于ASP.NET Core 3.X采用了不同的應(yīng)用承載方式,所以我們將這個(gè)模擬框架升級(jí)到3.x版本。[本篇內(nèi)容節(jié)選自即將出版的《ASP.NET Core 3框架解密》,感興趣的朋友可以通過(guò)《“ASP.NET Core 3框架揭秘”讀者群,歡迎加入》加入本書(shū)讀者群,以便及時(shí)了解本書(shū)的動(dòng)態(tài)。源代碼從這里下載。]
目錄
一、中間件委托鏈
???? HttpContext
???? 中間件
???? 中間件管道的構(gòu)建
二、服務(wù)器
???? IServer
???? 針對(duì)服務(wù)器的適配
???? HttpListenerServer
三、承載服務(wù)
???? WebHostedService
???? WebHostBuilder
???? 應(yīng)用構(gòu)建
一、中間件委托鏈
通過(guò)本篇文章,我將管道最核心的部分提取出來(lái)構(gòu)建一個(gè)“迷你版”的ASP.NET Core框架。較之真正的ASP.NET Core框架,雖然重建的模擬框架要簡(jiǎn)單很多,但是它們采用完全一致的設(shè)計(jì)。為了能夠在真實(shí)框架中找到對(duì)應(yīng)物,在定義接口或者類(lèi)型時(shí)會(huì)采用真實(shí)的名稱(chēng),但是在API的定義上會(huì)做最大限度的簡(jiǎn)化。
HttpContext
一個(gè)HttpContext對(duì)象表示針對(duì)當(dāng)前請(qǐng)求的上下文。要理解HttpContext上下文的本質(zhì),需要從請(qǐng)求處理管道的層面來(lái)講。對(duì)于由一個(gè)服務(wù)器和多個(gè)中間件構(gòu)成的管道來(lái)說(shuō),面向傳輸層的服務(wù)器負(fù)責(zé)請(qǐng)求的監(jiān)聽(tīng)、接收和最終的響應(yīng),當(dāng)它接收到客戶(hù)端發(fā)送的請(qǐng)求后,需要將請(qǐng)求分發(fā)給后續(xù)中間件進(jìn)行處理。對(duì)于某個(gè)中間件來(lái)說(shuō),完成自身的請(qǐng)求處理任務(wù)之后,在大部分情況下需要將請(qǐng)求分發(fā)給后續(xù)的中間件。請(qǐng)求在服務(wù)器與中間件之間,以及在中間件之間的分發(fā)是通過(guò)共享上下文的方式實(shí)現(xiàn)的。
如下圖所示,當(dāng)服務(wù)器接收到請(qǐng)求之后,會(huì)創(chuàng)建一個(gè)通過(guò)HttpContext表示的上下文對(duì)象,所有中間件都在這個(gè)上下文中完成針對(duì)請(qǐng)求的處理工作。那么一個(gè)HttpContext對(duì)象究竟會(huì)攜帶什么樣的上下文信息?一個(gè)HTTP事務(wù)(Transaction)具有非常清晰的界定,如果從服務(wù)器的角度來(lái)說(shuō)就是始于請(qǐng)求的接收,而終于響應(yīng)的回復(fù),所以請(qǐng)求和響應(yīng)是兩個(gè)基本的要素,也是HttpContext承載的最核心的上下文信息。
我們可以將請(qǐng)求和響應(yīng)理解為一個(gè)Web應(yīng)用的輸入與輸出,既然HttpContext上下文是針對(duì)請(qǐng)求和響應(yīng)的封裝,那么應(yīng)用程序就可以利用這個(gè)上下文對(duì)象得到當(dāng)前請(qǐng)求所有的輸入信息,也可以利用它完成我們所需的所有輸出工作。所以,我們?yōu)锳SP.NET Core模擬框架定義了如下這個(gè)極簡(jiǎn)版本的HttpContext類(lèi)型。
public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature {private readonly HttpListenerContext _context;public HttpListenerFeature(HttpListenerContext context)=> _context = context;Uri IHttpRequestFeature.Url=> _context.Request.Url;NameValueCollection IHttpRequestFeature.Headers=> _context.Request.Headers;NameValueCollection IHttpResponseFeature.Headers=> _context.Response.Headers;Stream IHttpRequestFeature.Body=> _context.Request.InputStream;Stream IHttpResponseFeature.Body=> _context.Response.OutputStream;int IHttpResponseFeature.StatusCode{get => _context.Response.StatusCode;set => _context.Response.StatusCode = value;} }如上面的代碼片段所示,我們可以利用HttpRequest對(duì)象得到當(dāng)前請(qǐng)求的地址、請(qǐng)求消息的報(bào)頭集合和主體內(nèi)容。利用HttpResponse對(duì)象,我們不僅可以設(shè)置響應(yīng)的狀態(tài)碼,還可以添加任意的響應(yīng)報(bào)頭和寫(xiě)入任意的主體內(nèi)容。
中間件
HttpContext對(duì)象承載了所有與當(dāng)前請(qǐng)求相關(guān)的上下文信息,應(yīng)用程序針對(duì)請(qǐng)求的響應(yīng)也利用它來(lái)完成,所以可以利用一個(gè)Action<HttpContext>類(lèi)型的委托對(duì)象來(lái)表示針對(duì)請(qǐng)求的處理,我們姑且將它稱(chēng)為請(qǐng)求處理器(Handler)。但Action<HttpContext>僅僅是請(qǐng)求處理器針對(duì)“同步”編程模式的表現(xiàn)形式,對(duì)于面向Task的異步編程模式,這個(gè)處理器應(yīng)該表示成類(lèi)型為Func<HttpContext,Task>的委托對(duì)象。
由于這個(gè)表示請(qǐng)求處理器的委托對(duì)象具有非常廣泛的應(yīng)用,所以我們?yōu)樗鼘?zhuān)門(mén)定義了如下這個(gè)RequestDelegate委托類(lèi)型,可以看出它就是對(duì)Func<HttpContext,Task>委托的表達(dá)。一個(gè)RequestDelegate對(duì)象表示的是請(qǐng)求處理器,那么中間件在模型中應(yīng)如何表達(dá)?
public delegate Task RequestDelegate(HttpContext context);作為請(qǐng)求處理管道核心組成部分的中間件可以表示成類(lèi)型為Func<RequestDelegate, RequestDelegate>的委托對(duì)象。換句話(huà)說(shuō),中間件的輸入與輸出都是一個(gè)RequestDelegate對(duì)象。我們可以這樣來(lái)理解:對(duì)于管道中的某個(gè)中間件(下圖所示的第一個(gè)中間件)來(lái)說(shuō),后續(xù)中間件組成的管道體現(xiàn)為一個(gè)RequestDelegate對(duì)象,由于當(dāng)前中間件在完成了自身的請(qǐng)求處理任務(wù)之后,往往需要將請(qǐng)求分發(fā)給后續(xù)中間件進(jìn)行處理,所以它需要將后續(xù)中間件構(gòu)成的RequestDelegate對(duì)象作為輸入。
當(dāng)代表當(dāng)前中間件的委托對(duì)象執(zhí)行之后,如果將它自己“納入”這個(gè)管道,那么代表新管道的RequestDelegate對(duì)象就成為該委托對(duì)象執(zhí)行后的輸出結(jié)果,所以中間件自然就表示成輸入和輸出類(lèi)型均為RequestDelegate的Func<RequestDelegate, RequestDelegate>對(duì)象。
中間件管道的構(gòu)建
從事軟件行業(yè)10多年來(lái),筆者對(duì)架構(gòu)設(shè)計(jì)越來(lái)越具有這樣的認(rèn)識(shí):好的設(shè)計(jì)一定是“簡(jiǎn)單”的設(shè)計(jì)。所以在設(shè)計(jì)某個(gè)開(kāi)發(fā)框架時(shí)筆者的目標(biāo)是再簡(jiǎn)單點(diǎn)。上面介紹的請(qǐng)求處理管道的設(shè)計(jì)就具有“簡(jiǎn)單”的特質(zhì):Pipeline = Server + Middlewares。但是“再簡(jiǎn)單點(diǎn)”其實(shí)是可以的,我們可以將多個(gè)中間件組成一個(gè)單一的請(qǐng)求處理器。請(qǐng)求處理器可以通過(guò)RequestDelegate對(duì)象來(lái)表示,所以整個(gè)請(qǐng)求處理管道將具有更加簡(jiǎn)單的表達(dá):Pipeline = Server + RequestDelegate(見(jiàn)下圖12)。
表示中間件的Func<RequestDelegate, RequestDelegate>對(duì)象向表示請(qǐng)求處理器的RequestDelegate對(duì)象之間的轉(zhuǎn)換是通過(guò)IApplicationBuilder對(duì)象來(lái)完成的。從接口命名可以看出,IApplicationBuilder對(duì)象是用來(lái)構(gòu)建“應(yīng)用程序”(Application)的,實(shí)際上,由所有注冊(cè)中間件構(gòu)建的RequestDelegate對(duì)象就是對(duì)應(yīng)用程序的表達(dá),因?yàn)閼?yīng)用程序的意圖完全是由注冊(cè)的中間件達(dá)成的。
public interface IApplicationBuilder {RequestDelegate Build();IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); }如上所示的代碼片段是模擬框架對(duì)IApplicationBuilder接口的簡(jiǎn)化定義。它的Use方法用來(lái)注冊(cè)中間件,而B(niǎo)uild方法則將所有的中間件按照注冊(cè)的順序組裝成一個(gè)RequestDelegate對(duì)象。如下所示的代碼片段中ApplicationBuilder類(lèi)型是對(duì)該接口的默認(rèn)實(shí)現(xiàn)。我們給出的代碼片段還體現(xiàn)了這樣一個(gè)細(xì)節(jié):當(dāng)我們將注冊(cè)的中間件轉(zhuǎn)換成一個(gè)表示請(qǐng)求處理器的RequestDelegate對(duì)象時(shí),會(huì)在管道的尾端添加一個(gè)處理器用來(lái)響應(yīng)一個(gè)狀態(tài)碼為404的響應(yīng)。這個(gè)細(xì)節(jié)意味著如果沒(méi)有注冊(cè)任何的中間件或者所有注冊(cè)的中間件都將請(qǐng)求分發(fā)給后續(xù)管道,那么應(yīng)用程序會(huì)回復(fù)一個(gè)狀態(tài)碼為404的響應(yīng)。
public class ApplicationBuilder : IApplicationBuilder {private readonly IList<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();public RequestDelegate Build(){RequestDelegate next = context =>{context.Response.StatusCode = 404;return Task.CompletedTask;};foreach (var middleware in _middlewares.Reverse()){next = middleware.Invoke(next);}return next;}public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware){_middlewares.Add(middleware);return this;} }二、服務(wù)器
服務(wù)器在管道中的職責(zé)非常明確:負(fù)責(zé)HTTP請(qǐng)求的監(jiān)聽(tīng)、接收和最終的響應(yīng)。具體來(lái)說(shuō),啟動(dòng)后的服務(wù)器會(huì)綁定到指定的端口進(jìn)行請(qǐng)求監(jiān)聽(tīng)。一旦有請(qǐng)求抵達(dá),服務(wù)器會(huì)根據(jù)該請(qǐng)求創(chuàng)建代表請(qǐng)求上下文的HttpContext對(duì)象,并將該上下文分發(fā)給注冊(cè)的中間件進(jìn)行處理。當(dāng)中間件管道完成了針對(duì)請(qǐng)求的處理之后,服務(wù)器會(huì)將最終生成的響應(yīng)回復(fù)給客戶(hù)端。
IServer
在模擬的ASP.NET Core框架中,我們將服務(wù)器定義成一個(gè)極度簡(jiǎn)化的IServer接口。在如下所示的代碼片段中,IServer接口具有唯一的StartAsync方法來(lái)啟動(dòng)自身代表的服務(wù)器。服務(wù)器最終需要將接收的請(qǐng)求分發(fā)給注冊(cè)的中間件,而注冊(cè)的中間件最終會(huì)被IApplicationBuilder對(duì)象構(gòu)建成一個(gè)代表請(qǐng)求處理器的RequestDelegate對(duì)象,StartAsync方法的參數(shù)handler代表的就是這樣一個(gè)對(duì)象。
public interface IServer {Task StartAsync(RequestDelegate handler); }針對(duì)服務(wù)器的適配
面向應(yīng)用層的HttpContext對(duì)象是對(duì)請(qǐng)求和響應(yīng)的抽象與封裝,但是請(qǐng)求最初是由面向傳輸層的服務(wù)器接收的,最終的響應(yīng)也會(huì)由服務(wù)器回復(fù)給客戶(hù)端。所有ASP.NET Core應(yīng)用使用的都是同一個(gè)HttpContext類(lèi)型,但是它們可以注冊(cè)不同類(lèi)型的服務(wù)器,應(yīng)如何解決兩者之間的適配問(wèn)題?計(jì)算機(jī)領(lǐng)域有這樣一句話(huà):“任何問(wèn)題都可以通過(guò)添加一個(gè)抽象層的方式來(lái)解決,如果解決不了,那就再加一層。”同一個(gè)HttpContext類(lèi)型與不同服務(wù)器類(lèi)型之間的適配問(wèn)題自然也可以通過(guò)添加一個(gè)抽象層來(lái)解決。我們將定義在該抽象層的對(duì)象稱(chēng)為特性(Feature),特性可以視為對(duì)HttpContext某個(gè)方面的抽象化描述。
如上圖所示,我們可以定義一系列特性接口來(lái)為HttpContext提供某個(gè)方面的上下文信息,具體的服務(wù)器只需要實(shí)現(xiàn)這些Feature接口即可。對(duì)于所有用來(lái)定義特性的接口,最重要的是提供請(qǐng)求信息的IRequestFeature接口和完成響應(yīng)的IResponseFeature接口。
下面闡述用來(lái)適配不同服務(wù)器類(lèi)型的特性在代碼層面的定義。如下面的代碼片段所示,我們定義了一個(gè)IFeatureCollection接口來(lái)表示存放特性的集合。可以看出,這是一個(gè)以Type和Object作為Key和Value的字典,Key代表注冊(cè)Feature所采用的類(lèi)型,而Value代表Feature對(duì)象本身,也就是說(shuō),我們提供的特性最終是以對(duì)應(yīng)類(lèi)型(一般為接口類(lèi)型)進(jìn)行注冊(cè)的。為了便于編程,我們定義了Set<T>方法和Get<T>方法來(lái)設(shè)置與獲取特性對(duì)象。
public interface IFeatureCollection : IDictionary<Type, object> { } public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection { } public static partial class Extensions {public static T Get<T>(this IFeatureCollection features) => features.TryGetValue(typeof(T), out var value) ? (T)value : default(T);public static IFeatureCollection Set<T>(this IFeatureCollection features, T feature){features[typeof(T)] = feature;return features;} }最核心的兩種特性類(lèi)型就是分別用來(lái)表示請(qǐng)求和響應(yīng)的特性,我們可以采用如下兩個(gè)接口來(lái)表示。可以看出,IHttpRequestFeature接口和IHttpResponseFeature接口具有與抽象類(lèi)型HttpRequest和HttpResponse完全一致的成員定義。
public interface IHttpRequestFeature {Uri Url { get; }NameValueCollection Headers { get; }Stream Body { get; } } public interface IHttpResponseFeature {int StatusCode { get; set; }NameValueCollection Headers { get; }Stream Body { get; } }我們?cè)谇懊娼o出了用于描述請(qǐng)求上下文的HttpContext類(lèi)型的成員定義,下面介紹其具體實(shí)現(xiàn)。如下面的代碼片段所示,表示請(qǐng)求和響應(yīng)的HttpRequest與HttpResponse分別是由對(duì)應(yīng)的特性(IHttpRequestFeature對(duì)象和IHttpResponseFeature對(duì)象)創(chuàng)建的。HttpContext對(duì)象本身則是通過(guò)一個(gè)表示特性集合的IFeatureCollection 對(duì)象來(lái)創(chuàng)建的,它會(huì)在初始化過(guò)程中從這個(gè)集合中提取出對(duì)應(yīng)的特性來(lái)創(chuàng)建HttpRequest對(duì)象和HttpResponse對(duì)象。
public class HttpContext {public HttpRequest Request { get; }public HttpResponse Response { get; }public HttpContext(IFeatureCollection features){Request = new HttpRequest(features);Response = new HttpResponse(features);} }public class HttpRequest {private readonly IHttpRequestFeature _feature;public Uri Url=> _feature.Url;public NameValueCollection Headers=> _feature.Headers;public Stream Body=> _feature.Body;public HttpRequest(IFeatureCollection features)=> _feature = features.Get<IHttpRequestFeature>(); }public class HttpResponse {private readonly IHttpResponseFeature _feature;public NameValueCollection Headers=> _feature.Headers;public Stream Body=> _feature.Body;public int StatusCode{get => _feature.StatusCode;set => _feature.StatusCode = value;}public HttpResponse(IFeatureCollection features)=> _feature = features.Get<IHttpResponseFeature>(); }換句話(huà)說(shuō),我們利用HttpContext對(duì)象的Request屬性提取的請(qǐng)求信息最初來(lái)源于IHttpRequestFeature對(duì)象,利用它的Response屬性針對(duì)響應(yīng)所做的任意操作最終都會(huì)作用到IHttpResponseFeature對(duì)象上。這兩個(gè)對(duì)象最初是由注冊(cè)的服務(wù)器提供的,這正是同一個(gè)ASP.NET Core應(yīng)用可以自由地選擇不同服務(wù)器類(lèi)型的根源所在。
HttpListenerServer
在對(duì)服務(wù)器的職責(zé)和它與HttpContext的適配原理有了清晰的認(rèn)識(shí)之后,我們可以嘗試定義一個(gè)服務(wù)器。我們將接下來(lái)定義的服務(wù)器類(lèi)型命名為HttpListenerServer,因?yàn)樗鼘?duì)請(qǐng)求的監(jiān)聽(tīng)、接收和響應(yīng)是由一個(gè)HttpListener對(duì)象來(lái)實(shí)現(xiàn)的。由于服務(wù)器接收到請(qǐng)求之后需要借助“特性”的適配來(lái)構(gòu)建統(tǒng)一的請(qǐng)求上下文(即HttpContext對(duì)象),這也是中間件的執(zhí)行上下文,所以提供針對(duì)性的特性實(shí)現(xiàn)是自定義服務(wù)類(lèi)型的關(guān)鍵所在。
對(duì)HttpListener有所了解的讀者都知道,當(dāng)它在接收到請(qǐng)求之后同樣會(huì)創(chuàng)建一個(gè)HttpListenerContext對(duì)象表示請(qǐng)求上下文。如果使用HttpListener對(duì)象作為ASP.NET Core應(yīng)用的監(jiān)聽(tīng)器,就意味著不僅所有的請(qǐng)求信息會(huì)來(lái)源于這個(gè)HttpListenerContext對(duì)象,我們針對(duì)請(qǐng)求的響應(yīng)最終也需要利用這個(gè)上下文對(duì)象來(lái)完成。HttpListenerServer對(duì)應(yīng)特性所起的作用實(shí)際上就是在HttpListenerContext和HttpContext這兩種上下文之間搭建起一座如下圖所示的橋梁。
上圖中用來(lái)在HttpListenerContext和HttpContext這兩個(gè)上下文類(lèi)型之間完成適配的特性類(lèi)型被命名為HttpListenerFeature。如下面的代碼片段所示,HttpListenerFeature類(lèi)型同時(shí)實(shí)現(xiàn)了針對(duì)請(qǐng)求和響應(yīng)的特性接口IHttpRequestFeature與IHttpResponseFeature。
public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature {private readonly HttpListenerContext _context;public HttpListenerFeature(HttpListenerContext context) => _context = context;Uri IHttpRequestFeature.Url => _context.Request.Url;NameValueCollection IHttpRequestFeature.Headers => _context.Request.Headers;NameValueCollection IHttpResponseFeature.Headers => _context.Response.Headers;Stream IHttpRequestFeature.Body => _context.Request.InputStream;Stream IHttpResponseFeature.Body => _context.Response.OutputStream;int IHttpResponseFeature.StatusCode{get => _context.Response.StatusCode;set => _context.Response.StatusCode = value;} }創(chuàng)建HttpListenerFeature對(duì)象時(shí)需要提供一個(gè)HttpListenerContext對(duì)象,IHttpRequestFeature接口的實(shí)現(xiàn)成員所提供的請(qǐng)求信息全部來(lái)源于這個(gè)HttpListenerContext上下文,IHttpResponseFeature接口的實(shí)現(xiàn)成員針對(duì)響應(yīng)的操作最終也轉(zhuǎn)移到這個(gè)HttpListenerContext上下文上。如下所示的代碼片段是針對(duì)HttpListener的服務(wù)器類(lèi)型HttpListenerServer的完整定義。我們?cè)趧?chuàng)建HttpListenerServer對(duì)象的時(shí)候可以顯式提供一組監(jiān)聽(tīng)地址,如果沒(méi)有提供,監(jiān)聽(tīng)地址會(huì)默認(rèn)設(shè)置“l(fā)ocalhost:5000”。在實(shí)現(xiàn)的StartAsync方法中,我們啟動(dòng)了在構(gòu)造函數(shù)中創(chuàng)建的HttpListenerServer對(duì)象,并且在一個(gè)無(wú)限循環(huán)中通過(guò)調(diào)用其GetContextAsync方法實(shí)現(xiàn)了針對(duì)請(qǐng)求的監(jiān)聽(tīng)和接收。
public class HttpListenerServer : IServer {private readonly HttpListener _httpListener;private readonly string[] _urls;public HttpListenerServer(params string[] urls){_httpListener = new HttpListener();_urls = urls.Any() ? urls : new string[] { "http://localhost:5000/" };}public async Task StartAsync(RequestDelegate handler){Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));_httpListener.Start();while (true){var listenerContext = await _httpListener.GetContextAsync();var feature = new HttpListenerFeature(listenerContext);var features = new FeatureCollection().Set<IHttpRequestFeature>(feature).Set<IHttpResponseFeature>(feature);var httpContext = new HttpContext(features);await handler(httpContext);listenerContext.Response.Close();}} }當(dāng)HttpListener監(jiān)聽(tīng)到抵達(dá)的請(qǐng)求后,我們會(huì)得到一個(gè)HttpListenerContext對(duì)象,此時(shí)只需要利用它創(chuàng)建一個(gè)HttpListenerFeature對(duì)象并且分別以IHttpRequestFeature接口和IHttpResponseFeature接口的形式注冊(cè)到創(chuàng)建的FeatureCollection集合上。我們最終利用這個(gè)FeatureCollection集合創(chuàng)建出代表請(qǐng)求上下文的HttpContext對(duì)象,當(dāng)將它作為參數(shù)調(diào)用由所有注冊(cè)中間件共同構(gòu)建的RequestDelegate對(duì)象時(shí),中間件管道將接管并處理該請(qǐng)求。
三、承載服務(wù)
到目前為止,我們已經(jīng)了解構(gòu)成ASP.NET Core請(qǐng)求處理管道的兩個(gè)核心要素(服務(wù)器和中間件),現(xiàn)在我們的目標(biāo)是利用.NET Core承載服務(wù)系統(tǒng)來(lái)承載這一管道。毫無(wú)疑問(wèn),還需要通過(guò)實(shí)現(xiàn)IHostedService接口來(lái)定義對(duì)應(yīng)的承載服務(wù),為此我們定義了一個(gè)名為WebHostedService的承載服務(wù)。(關(guān)于.NET Core承載服務(wù)系統(tǒng),請(qǐng)參閱我的系列文章《服務(wù)承載系統(tǒng)》)
WebHostedService
由于服務(wù)器是整個(gè)請(qǐng)求處理管道的“龍頭”,所以從某種意義上來(lái)說(shuō),啟動(dòng)一個(gè)ASP.NET Core應(yīng)用就是為啟動(dòng)服務(wù)器,所以可以將服務(wù)的啟動(dòng)在WebHostedService承載服務(wù)中實(shí)現(xiàn)。如下面的代碼片段所示,創(chuàng)建一個(gè)WebHostedService對(duì)象時(shí),需要提供服務(wù)器對(duì)象和由所有注冊(cè)中間件構(gòu)建的RequestDelegate對(duì)象。在實(shí)現(xiàn)的StartAsync方法中,我們只需要調(diào)用服務(wù)器對(duì)象的StartAsync方法啟動(dòng)它即可。
public class WebHostedService : IHostedService {private readonly IServer _server;private readonly RequestDelegate _handler;public WebHostedService(IServer server, RequestDelegate handler){_server = server;_handler = handler;}public Task StartAsync(CancellationToken cancellationToken) => _server.StartAsync(_handler);public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; }到目前為止,我們基本上已經(jīng)完成了所有核心的工作,如果能夠?qū)⒁粋€(gè)WebHostedService實(shí)例注冊(cè)到.NET Core的承載系統(tǒng)中,它就能夠幫助我們啟動(dòng)一個(gè)ASP.NET Core應(yīng)用。為了使這個(gè)過(guò)程在編程上變得更加便利和“優(yōu)雅”,我們定義了一個(gè)輔助的WebHostBuilder類(lèi)型。
WebHostBuilder
要?jiǎng)?chuàng)建一個(gè)WebHostedService對(duì)象,必需顯式地提供一個(gè)表示服務(wù)器的IServer對(duì)象,以及由所有注冊(cè)中間件構(gòu)建而成的RequestDelegate對(duì)象,WebHostBuilder提供了更加便利和“優(yōu)雅”的服務(wù)器與中間件注冊(cè)方式。如下面的代碼片段所示,WebHostBuilder是對(duì)額外兩個(gè)Builder對(duì)象的封裝:一個(gè)是用來(lái)構(gòu)建服務(wù)宿主的IHostBuilder對(duì)象,另一個(gè)是用來(lái)注冊(cè)中間件并最終幫助我們創(chuàng)建RequestDelegate對(duì)象的IApplicationBuilder對(duì)象。
public class WebHostBuilder {public IHostBuilder HostBuilder { get; }public IApplicationBuilder ApplicationBuilder { get; }public WebHostBuilder(IHostBuilder hostBuilder, IApplicationBuilder applicationBuilder){HostBuilder = hostBuilder;ApplicationBuilder = applicationBuilder;} }我們?yōu)閃ebHostBuilder定義了如下兩個(gè)擴(kuò)展方法:UseHttpListenerServer方法完成了針對(duì)自定義的服務(wù)器類(lèi)型HttpListenerServer的注冊(cè);Configure方法提供了一個(gè)Action<IApplication
Builder>類(lèi)型的參數(shù),利用該參數(shù)來(lái)注冊(cè)任意中間件。
代表ASP.NET Core應(yīng)用的請(qǐng)求處理管道最終是利用承載服務(wù)WebHostedService注冊(cè)到.NET Core的承載系統(tǒng)中的,針對(duì)WebHostedService服務(wù)的創(chuàng)建和注冊(cè)體現(xiàn)在為IHostBuilder接口定義的ConfigureWebHost擴(kuò)展方法上。如下面的代碼片段所示,ConfigureWebHost方法定義了一個(gè)Action<WebHostBuilder>類(lèi)型的參數(shù),利用該參數(shù)可以注冊(cè)服務(wù)器、中間件及其他相關(guān)服務(wù)。
public static partial class Extensions {public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<WebHostBuilder> configure){var webHostBuilder = new WebHostBuilder(builder, new ApplicationBuilder());configure?.Invoke(webHostBuilder);builder.ConfigureServices(svcs => svcs.AddSingleton<IHostedService>(provider => {var server = provider.GetRequiredService<IServer>();var handler = webHostBuilder.ApplicationBuilder.Build();return new WebHostedService(server, handler);}));return builder;} }在ConfigureWebHost方法中,我們創(chuàng)建了一個(gè)ApplicationBuilder對(duì)象,并利用它和當(dāng)前的IHostBuilder對(duì)象創(chuàng)建了一個(gè)WebHostBuilder對(duì)象,然后將這個(gè)WebHostBuilder對(duì)象作為參數(shù)調(diào)用了指定的Action<WebHostBuilder>委托對(duì)象。在此之后,我們調(diào)用IHostBuilder接口的ConfigureServices方法在依賴(lài)注入框架中注冊(cè)了一個(gè)用于創(chuàng)建WebHostedService服務(wù)的工廠(chǎng)。對(duì)于由該工廠(chǎng)創(chuàng)建的WebHostedService對(duì)象來(lái)說(shuō),服務(wù)器來(lái)源于注冊(cè)的服務(wù),而作為請(qǐng)求處理器的RequestDelegate對(duì)象則由ApplicationBuilder對(duì)象根據(jù)注冊(cè)的中間件構(gòu)建而成。
應(yīng)用構(gòu)建
到目前為止,這個(gè)用來(lái)模擬ASP.NET Core請(qǐng)求處理管道的“迷你版”框架已經(jīng)構(gòu)建完成,下面嘗試在它上面開(kāi)發(fā)一個(gè)簡(jiǎn)單的應(yīng)用。如下面的代碼片段所示,我們調(diào)用靜態(tài)類(lèi)型Host的CreateDefaultBuilder方法創(chuàng)建了一個(gè)IHostBuilder對(duì)象,然后調(diào)用ConfigureWebHost方法并利用提供的Action<WebHostBuilder>對(duì)象注冊(cè)了HttpListenerServer服務(wù)器和3個(gè)中間件。在調(diào)用Build方法構(gòu)建出作為服務(wù)宿主的IHost對(duì)象之后,我們調(diào)用其Run方法啟動(dòng)所有承載的IHostedSerivce服務(wù)。
class Program {static void Main(){Host.CreateDefaultBuilder().ConfigureWebHost(builder => builder.UseHttpListenerServer().Configure(app => app.Use(FooMiddleware).Use(BarMiddleware).Use(BazMiddleware))).Build().Run();}public static RequestDelegate FooMiddleware(RequestDelegate next)=> async context =>{await context.Response.WriteAsync("Foo=>");await next(context);};public static RequestDelegate BarMiddleware(RequestDelegate next)=> async context =>{await context.Response.WriteAsync("Bar=>");await next(context);};public static RequestDelegate BazMiddleware(RequestDelegate next)=> context => context.Response.WriteAsync("Baz"); }由于中間件最終體現(xiàn)為一個(gè)類(lèi)型為Func<RequestDelegate, RequestDelegate>的委托對(duì)象,所以可以利用與之匹配的方法來(lái)定義中間件。演示實(shí)例中定義的3個(gè)中間件(FooMiddleware、BarMiddleware和BazMiddleware)對(duì)應(yīng)的正是3個(gè)靜態(tài)方法,它們調(diào)用WriteAsync擴(kuò)展方法在響應(yīng)中寫(xiě)了一段文字。
public static partial class Extensions {public static Task WriteAsync(this HttpResponse response, string contents){var buffer = Encoding.UTF8.GetBytes(contents);return response.Body.WriteAsync(buffer, 0, buffer.Length);} }應(yīng)用啟動(dòng)之后,如果利用瀏覽器向應(yīng)用程序采用的默認(rèn)監(jiān)聽(tīng)地址(“http://localhost:5000”)發(fā)送一個(gè)請(qǐng)求,得到的輸出結(jié)果如下圖所示。瀏覽器上呈現(xiàn)的文字正是注冊(cè)的3個(gè)中間件寫(xiě)入的。
總結(jié)
以上是生活随笔為你收集整理的200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: .NET Core开发实战(第21课:中
- 下一篇: Asp.Net Core 中Identi