200行程式碼,7個物件——讓你瞭解ASP.NET Core框架的本質
2019年1月19日,微軟技術(蘇州)俱樂部成立,我受邀在成立大會上作了一個名為《ASP.NET Core框架揭祕》的分享。在此次分享中,我按照ASP.NET Core自身的執行原理和設計思想建立了一個 “迷你版” 的ASP.NET Core框架,並且利用這個 “極簡” 的模擬框架闡述了ASP.NET Core框架最核心、最本質的東西。整個框架涉及到的核心程式碼不會超過200行,涉及到7個核心的物件。
目錄
1. 從Hello World談起
2. ASP.NET Core Mini
3. Hello World 2
4. 第一個物件:HttpContext
5. 第二個物件:RequetDelegate
6. 第三個物件:Middleware
7. 第四個物件:ApplicationBuilder
8. 第五個物件:Server
9. HttpContext和Server之間的適配
10. HttpListenerServer
11. 第六個物件:WebHost
12. 第七個物件:WebHostBuilder
13. 回顧一下Hello World 2
14. 打個廣告:《ASP.NET Core框架揭祕》
1、從Hello World談起
當我們最開始學習一門技術的時候都喜歡從Hello World來時,貌似和我們本篇的主題不太搭。但事實卻非如此,在我們看來如下這個Hello World是對ASP.NET Core框架本質最好的體現。
public class Program { public static void Main() => new WebHostBuilder() .UseKestrel() .Configure(app => app.Run(context => context.Response.WriteAsync("Hello World!"))) .Build() .Run(); }
如上這個Hello World程式雖然人為地劃分為若干行,但是整個應用程式其實只有一個語句。這個語句涉及到了ASP.NET Core程式兩個核心物件WebHost和WebHostBuilder。我們可以將WebHost理解為寄宿或者承載Web應用的宿主,應用的啟動可以通過啟動作為宿主的WebHost來實現。至於WebHostBuilder,顧名思義,就是WebHost的構建者。
在呼叫WebHostBuilder的Build方法創建出WebHost之前,我們呼叫了它的兩個方法,其中UseKestrel旨在註冊一個名為Kestrel的伺服器,而Configure方法的呼叫則是為了註冊一個用來處理請求的中介軟體,後者在響應的主體內容中寫入一個“Hello World”文字。
當我們呼叫Run方法啟動作為應用宿主的WebHost的時候,後者會利用WebHostBuilder提供的伺服器和中介軟體構建一個請求處理管道。這個由一個伺服器和若干中介軟體構成的管道就是ASP.NET Core框架的核心,我們接下來的核心任務就是讓大家搞清楚這個管道是如何被構建起來的,以及該管道採用怎樣的請求處理流程。
2、ASP.NET CoreMini
在過去這些年中,我不斷地被問到同一個問題:如何深入地去一個開發框架。我知道每個人都具有適合自己的學習方式,而且我覺得我個人的學習方法也算不上高效,所以我很少會正面迴應這個問題。不過有一個方法我倒很樂意與大家分享,那就是當你在學習一個開發框架的時候不要只關注程式設計層面的東西,而應該將更多的精力集中到對架構設計層面的學習。
針對某個框架來說,它提供的程式設計模式紛繁複雜,而底層的設計原理倒顯得簡單明瞭。那麼如何檢驗我們對框架的設計原理是否透徹呢,我覺得最好的方式就是根據你的理解對框架進行“再造”。當你按照你的方式對框架進行“重建”的過程中,你會發現很多遺漏的東西。如果被你重建的框架能夠支撐一個可以執行的Hello World應用,那麼可以基本上證明你已經基本理解了這個框架最本質的東西。
雖然ASP.NET Core目前是一個開源的專案,我們可以完全通過原始碼來學習它,但是我相信這對於絕大部分人來說是有難度的。為此我們將ASP.NET Core最本質、最核心的部分提取出來,重新構建了一個迷你版的ASP.NET Core框架。
ASP.NET Core Mini具有如上所示的三大特點。第一、它是對真實ASP.NET Core框架的真實模擬,所以在部分API的定義上我們做了最大限度的簡化,但是兩者的本質是完全一致的。如果你能理解ASP.NET Core Mini,意味著你也就是理解了真實ASP.NET Core框架。第二、這個框架是可執行的,我們提供的並不是虛擬碼。第三、為了讓大家能夠在最短的時間內理解ASP.NET Core框架的精髓,ASP.NET Core Mini必需足夠簡單,所以我們整個實現的核心程式碼不會超過200行。
3、Hello World 2
既然我們的ASP.NET Core Mini是可執行的,意味著我們可以在上面構建我們自己的應用,如下所示的就是在ASP.NET Core Mini上面開發的Hello World,可以看出它採用了與真實ASP.NET Core框架一致的程式設計模式。
public class Program { public static async Task Main() { await new WebHostBuilder() .UseHttpListener() .Configure(app => app .Use(FooMiddleware) .Use(BarMiddleware) .Use(BazMiddleware)) .Build() .StartAsync(); } 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"); }
我們有必要對上面這個Hello World程式作一個簡答的介紹:在創建出WebHostBuilder之後,我們呼叫了它的擴充套件方法UseHttpListener註冊了一個自定義的基於HttpListener的伺服器,我們會在後續內容中介紹該伺服器的實現。在隨後針對Configure方法的呼叫中,我們註冊了三個中介軟體。由於中介軟體最終是通過Delegate物件來體現的,所以我們可以將中介軟體定義成與Delegate型別具有相同簽名的方法。
我們目前可以先不用考慮表示中介軟體的三個方法為什麼需要成如上的形式,只需要知道三個中介軟體在針對請求的處理流程中都作了些什麼。上面的程式碼很清楚,三個中介軟體分別會在響應的內容中寫入一段文字,所以程式執行後,如果我們利用瀏覽器訪問該應用,會得到如下所示的輸出結果。
4、第一個物件:HttpContext
正如本篇文章表示所說,我們的ASP.NET Core Mini由7個核心物件構建而成。第一個就是大家非常熟悉的HttpContext物件,它可以說是ASP.NET Core應用開發中使用頻率最高的物件。要說明HttpContext的本質,還得從請求處理管道的層面來講。對於由一個伺服器和多箇中間件構建的管道來說,面向傳輸層的伺服器負責請求的監聽、接收和最終的響應,當它接收到客戶端傳送的請求後,需要將它分發給後續中介軟體進行處理。對於某個中介軟體來說,當我們完成了自身的請求處理任務之後,在大部分情況下也需要將請求分發給後續的中介軟體。請求在伺服器與中介軟體之間,以及在中介軟體之間的分發是通過共享上下文的方式實現的。
如上圖所示,當伺服器接收到請求之後,會建立一個通過HttpContext表示的上下文物件,所有中介軟體都是在這個上下文中處理請求的,那麼一個HttpContext物件究竟攜帶怎樣的上下文資訊呢?我們知道一個HTTP事務(Transaction)具有非常清晰的界定,即接收請求、傳送響應,所以請求和響應是兩個基本的要素,也是HttpContext承載的最核心的上下文資訊。
我們可以將請求理解為輸入、響應理解為輸出,所以應用程式可以利用HttpContext得到當前請求所有的輸入資訊,也可以利用它完成我們所需的所有輸出工作。為此我們為ASP.NET Core Mini定義瞭如下這個極簡版本的HttpContext。
public class HttpContext { publicHttpRequest Request { get; } publicHttpResponse Response { get; } } public class HttpRequest { publicUri Url { get; } publicNameValueCollection Headers { get; } publicStream Body { get; } } public class HttpResponse { publicNameValueCollection Headers { get; } publicStream Body { get; } public int StatusCode { get; set;} }
如上面的程式碼片段所示,HttpContext通過它的兩個屬性Request和Response來表示請求和響應,它們對應的型別分別為HttpRequest和HttpResponse。通過前者,我們可以得到請求的地址、手部集合和主體內容,利用後者,我們可以設定響應狀態碼,也可以設定首部和主體內容。
5、第二個物件:RequestDelegate
RequestDelegate是我們介紹的第二個核心物件。我們從命名可以看出這是一個委託(Delegate)物件,和上面介紹的HttpContext一樣,我們也只有從管道的角度才能充分理解這個委託物件的本質。
在從事軟體行業10多年來,我對軟體的架構設計越來越具有這樣的認識:好的設計一定是“簡單”的設計。所以每當我在設計某個開發框架的時候,一直會不斷告訴我自己:“還能再簡單點嗎?”。我們上面介紹的ASP.NET Core管道的設計就具有“簡單”的特質:Pipeline = Server + Middlewares。但是“還能再簡單點嗎?”,其實是可以的:我們可以將多個Middleware構建成一個單一的“HttpHandler”,那麼整個ASP.NET Core框架將具有更加簡單的表達:Pipeline =Server + HttpHandler。
那麼我們如來表達HttpHandler呢?我們可以這樣想:既然針對當前請求的所有輸入和輸出都通過HttpContext來表示,那麼HttpHandler就可以表示成一個Action<HttpContext>物件。那麼HttpHandler在ASP.NET Core中是通過Action<HttpContext>來表示的嗎?其實不是的,原因很簡單:Action<HttpContext>只能表示針對請求的 “同步” 處理操作,但是針對HTTP請求既可以是同步的,也可以是非同步的,更多地其實是非同步的。
那麼在.NET Core的世界中如何來表示一個同步或者非同步操作呢?你應該想得到,那就是Task物件,那麼HttpHandler自然就可以表示為一個Func<HttpContext,Task>物件。由於這個委託物件實在太重要了,所以我們將它定義成一個獨立的型別。
6、第三個物件:Middleware
在對RequestDelegate這個委託物件具有充分認識之後,我們來聊聊中介軟體又如何表達,這也是我們介紹的第三個核心物件。中介軟體在ASP.NET Core被表示成一個Func<RequestDelegate, RequestDelegate>物件,也就是說它的輸入和輸出都是一個RequestDelegate。
對於為什麼會採用一個Func<RequestDelegate, RequestDelegate>物件來表示中介軟體,很多初學者會很難理解。我們可以這樣的考慮:對於管道的中的某一箇中間件來說,由後續中介軟體組成的管道體現為一個RequestDelegate物件,由於當前中介軟體在完成了自身的請求處理任務之後,往往需要將請求分發給後續中介軟體進行處理,所有它它需要將由後續中介軟體構成的RequestDelegate作為輸入。
當代表中介軟體的委託物件執行之後,我們希望的是將當前中介軟體“納入”這個管道,那麼新的管道體現的RequestDelegate自然成為了輸出結果。所以中介軟體自然就表示成輸入和輸出均為RequestDelegate的Func<RequestDelegate, RequestDelegate>物件。
7、第四個物件:ApplicationBuilder
ApplicationBuilder是我們認識的第四個核心物件。從命名來看,這是我們接觸到的第二個Builder,既然它被命名為ApplicationBuilder,意味著由它構建的就是一個Application。那麼在ASP.NET Core框架的語義下應用(Application)又具有怎樣的表達呢?
對於這個問題,我們可以這樣來理解:既然Pipeline = Server + HttpHandler,那麼用來處理請求的HttpHandler不就承載了當前應用的所有職責嗎?那麼HttpHandler就等於Application,由於HttpHandler通過RequestDelegate表示,那麼由ApplicationBuilder構建的Application就是一個RequestDelegate物件。
由於表示HttpHandler的RequestDelegate是由註冊的中介軟體來構建的,所以ApplicationBuilder還具有註冊中介軟體的功能。基於ApplicationBuilder具有的這兩個基本職責,我們可以將對應的介面定義成如下的形式。Use方法用來註冊提供的中介軟體,Build方法則將註冊的中介軟體構建成一個RequestDelegate物件。
public interfaceIApplicationBuilder { IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); RequestDelegate Build(); }
如下所示的是針對該介面的具體實現。我們利用一個列表來儲存註冊的中介軟體,所以Use方法只需要將提供的中介軟體新增到這個列表中即可。當Build方法被呼叫之後,我們只需按照與註冊相反的順序依次執行表示中介軟體的Func<RequestDelegate, RequestDelegate>物件就能最終構建出代表HttpHandler的RequestDelegate物件。
public class ApplicationBuilder : IApplicationBuilder { private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>(); public RequestDelegate Build() { return httpContext => { RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; }; _middlewares.Reverse(); foreach (var middleware in _middlewares) { next = middleware(next); } return next(httpContext); }; } public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _middlewares.Add(middleware); return this; } }
在呼叫第一個中介軟體(最後註冊)的時候,我們建立了一個RequestDelegate作為輸入,後者會將響應狀態碼設定為404。所以如果ASP.NET Core應用在沒有註冊任何中間的情況下總是會返回一個404的響應。如果所有的中介軟體在完成了自身的請求處理任務之後都選擇將請求向後分發,同樣會返回一個404響應。
8、第五個物件:Server
伺服器在管道中的職責非常明確,當我們自動作應用宿主的WebHost的時候,服務它被自動啟動。啟動後的伺服器會繫結到指定的埠進行請求監聽,一旦有請求抵達,伺服器會根據該請求創建出代表上下文的HttpContext物件,並將該上下文作為輸入呼叫由所有註冊中介軟體構建而成的RequestDelegate物件。
簡單起見,我們使用如下這個簡寫的IServer介面來表示伺服器。我們通過定義在IServer介面的唯一方法StartAsync啟動伺服器,作為引數的handler正是由所有註冊中介軟體共同構建而成的RequestDelegate物件
public interface IServer { Task StartAsync(RequestDelegate handler); }
9、HttpContext和Server之間的適配
面向應用層的HttpContext物件是對請求和響應的封裝,但是請求最初來源於伺服器,針對HttpContext的任何響應操作也必需作用於當前的伺服器才能真正起作用。現在問題來了,所有的ASP.NET Core應用使用的都是同一個HttpContext型別,但是卻可以註冊不同型別的伺服器,我們必需解決兩者之間的適配問題。
計算機領域有一句非常經典的話:“任何問題都可以通過新增一個抽象層的方式來解決,如果解決不了,那就再加一層”。同一個HttpContext型別與不同伺服器型別之間的適配問題也可可以通過新增一個抽象層來解決,我們定義在該層的物件稱為Feature。如上圖所示,我們可以定義一系列的Feature介面來為HttpContext提供上下文資訊,其中最重要的就是提供請求的IRequestFeature和完成響應的IResponseFeature介面。那麼具體的伺服器只需要實現這些Feature介面就可以了。
我們接著從程式碼層面來看看具體的實現。如下面的程式碼片段所示,我們定義了一個IFeatureCollection介面來表示存放Feature物件的集合。從定義可以看出這是一個以Type和Object作為Key和Value的字典,Key代表註冊Feature所採用的型別,而Value自然就代表Feature物件本身,話句話說我們提供的Feature物件最終是以對應Feature型別(一般為介面型別)進行註冊的。為了程式設計上便利,我們定義了兩個擴充套件方法Set<T>和Get<T>來設定和獲取Feature物件。
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; } }
如下所示的用來提供請求和響應IHttpRequestFeature和IHttpResponseFeature介面的定義,可以看出它們具有與HttpRequest和HttpResponse完全一致的成員定義。
public interface IHttpRequestFeature { UriUrl { get; } NameValueCollectionHeaders { get; } StreamBody { get; } } public interface IHttpResponseFeature { intStatusCode { get; set; } NameValueCollectionHeaders { get; } StreamBody { get; } }
接下來我們來看看HttpContext的具體實現。ASP.NET Core Mini的HttpContext只包含Request和Response兩個屬性成員,對應的型別分別為HttpRequest和HttpResponse,如下所示的就是這兩個型別的具體實現。我們可以看出HttpRequest和HttpResponse都是通過一個IFeatureCollection物件構建而成的,它們對應的屬性成員均有分別由包含在這個Feature集合中的IHttpRequestFeature和IHttpResponseFeature物件來提供的。
public class HttpRequest { private readonly IHttpRequestFeature _feature; publicUri Url => _feature.Url; publicNameValueCollection Headers => _feature.Headers; publicStream Body => _feature.Body; public HttpRequest(IFeatureCollection features) => _feature = features.Get<IHttpRequestFeature>(); } public class HttpResponse { private readonly IHttpResponseFeature _feature; publicNameValueCollection Headers => _feature.Headers; publicStream Body => _feature.Body; public int StatusCode { get => _feature.StatusCode; set => _feature.StatusCode = value; } public HttpResponse(IFeatureCollection features) => _feature = features.Get<IHttpResponseFeature>(); }
HttpContext的實現就更加簡單了。如下面的程式碼片段所示,我們在建立一個HttpContext物件是同樣會提供一個IFeatureCollection物件,我們利用該物件建立對應的HttpRequest和HttpResponse物件,並作為對應的屬性值。
public class HttpContext { publicHttpRequest Request { get; } publicHttpResponse Response { get; } public HttpContext(IFeatureCollection features) { Request = new HttpRequest(features); Response = new HttpResponse(features); } }
10、HttpListenerServer
在對伺服器和它與HttpContext的適配原理具有清晰的認識之後,我們來嘗試著自己定義一個伺服器。在前面的Hello World例項中,我們利用WebHostBuilder的擴充套件方法UseHttpListener註冊了一個HttpListenerServer,我們現在就來看看這個採用HttpListener作為監聽器的伺服器型別是如何實現的。
由於所有的伺服器都需要自動自己的Feature實現來為HttpContext提供對應的上下文資訊,所以我們得先來為HttpListenerServer定義相應的介面。對HttpListener稍微瞭解的朋友應該知道它在接收到請求之後同行會建立一個自己的上下文物件,對應的型別為HttpListenerContext。如果採用HttpListenerServer作為應用的伺服器,意味著HttpContext承載的上下文資訊最初來源於這個HttpListenerContext,所以Feature的目的旨在解決這兩個上下文之間的適配問題。
如下所示的HttpListenerFeature就是我們為HttpListenerServer定義的Feature。HttpListenerFeature同時實現了IHttpRequestFeature和IHttpResponseFeature,實現的6個屬性成員最初都來源於建立該Feature物件提供的HttpListenerContext物件。
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; } }
如下所示的是HttpListenerServer的最終定義。我們在構造一個HttpListenerServer物件的時候可以提供一組監聽地址,如果沒有提供,會採用“localhost:5000”作為預設的監聽地址。在實現的StartAsync方法中,我們啟動了在建構函式中建立的HttpListenerServer物件,並在一個迴圈中通過呼叫其GetContextAsync方法實現了針對請求的監聽和接收。
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(); } } }
當HttpListener監聽到抵達的請求後,我們會得到一個HttpListenerContext物件,此時我們只需要據此建立一個HttpListenerFeature物件並它分別以IHttpRequestFeature和IHttpResponseFeature介面型別註冊到建立FeatureCollection集合上。我們最終利用這個FeatureCollection物件創建出代表上下文的HttpContext,然後將它作為引數呼叫由所有中介軟體共同構建的RequestDelegate物件即可。
11、第六個物件:WebHost
到目前為止我們已經知道了由一個伺服器和多箇中間件構成的管道是如何完整針對請求的監聽、接收、處理和最終響應的,接下來來討論這樣的管道是如何被構建出來的。管道是在作為應用宿主的WebHost物件啟動的時候被構建出來的,在ASP.NET Core Mini中,我們將表示應用宿主的IWebHost介面簡寫成如下的形式:只包含一個StartAsync方法用來啟動應用程式。
public interface IWebHost { Task StartAsync(); }
由於由WebHost構建的管道由Server和HttpHandler構成,我們在預設實現的WebHost型別中,我們直接提供者兩個物件。在實現的StartAsync方法中,我麼只需要將後者作為引數呼叫前者的StartAsync方法將伺服器啟動就可以了。
public class WebHost : IWebHost { private readonly IServer _server; private readonly RequestDelegate _handler; public WebHost(IServer server, RequestDelegate handler) { _server = server; _handler = handler; } public Task StartAsync() => _server.StartAsync(_handler); }
12、第七個物件:WebHostBuilder
作為最後一個著重介紹的核心物件,WebHostBuilder的使命非常明確:就是建立作為應用宿主的WebHost。由於在建立WebHost的時候需要提供註冊的伺服器和由所有註冊中介軟體構建而成的RequestDelegate,所以在對應介面IWebHostBuilder中,我們為它定義了三個核心方法。
public interface IWebHostBuilder { IWebHostBuilder UseServer(IServer server); IWebHostBuilder Configure(Action<IApplicationBuilder> configure); IWebHost Build(); }
除了用來建立WebHost的Build方法之外,我們提供了用來註冊伺服器的UseServer方法和用來註冊中介軟體的Configure方法。Configure方法提供了一個型別為Action<IApplicationBuilder>的引數,意味著我們針對中介軟體的註冊是利用上面介紹的IApplicationBuilder物件來完成的。
如下所示的WebHostBuilder是針對IWebHostBuilder介面的預設實現,它具有兩個欄位分別用來儲存註冊的中介軟體和呼叫Configure方法提供的Action<IApplicationBuilder>物件。當Build方法被呼叫之後,我們建立一個ApplicationBuilder物件,並將它作為引數呼叫這些Action<IApplicationBuilder>委託,進而將所有中介軟體全部註冊到這個ApplicationBuilder物件上。我們最終呼叫它的Build方法得到由所有中介軟體共同構建的RequestDelegate物件,並利用它和註冊的伺服器構建作為應用宿主的WebHost物件。
public class WebHostBuilder : IWebHostBuilder { private IServer _server; private readonly List<Action<IApplicationBuilder>> _configures = new List<Action<IApplicationBuilder>>(); public IWebHostBuilder Configure(Action<IApplicationBuilder> configure) { _configures.Add(configure); return this; } public IWebHostBuilder UseServer(IServer server) { _server = server; return this; } public IWebHost Build() { var builder = new ApplicationBuilder(); foreach (var configure in _configures) { configure(builder); } return new WebHost(_server, builder.Build()); } }
13、回顧一下Hello World 2
到目前為止,我們已經將ASP.NET Core Mini涉及的七個核心物件介紹完了,然後我們再來回顧一下建立在這個模擬框架上的Hello World程式。
public class Program { public static async Task Main() { await new WebHostBuilder() .UseHttpListener() .Configure(app => app .Use(FooMiddleware) .Use(BarMiddleware) .Use(BazMiddleware)) .Build() .StartAsync(); } 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"); }
首選我們呼叫WebHostBuilder的擴充套件方法UseHttpListener採用如下的方式完成了針對HttpListenerServer的註冊。由於中介軟體體現為一個Func<RequestDelegate, RequestDelegate>物件,我們自然可以採用與之具有相同宣告的方法(FooMiddleware、BarMiddleware和BazMiddleware)來定義對應的中介軟體。中介軟體呼叫HttpResponse的WriteAsync以如下的方式將指定的字串寫入響應主體的輸出流。
public static partial class Extensions { public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder, params string[] urls) => builder.UseServer(new HttpListenerServer(urls)); public static Task WriteAsync(this HttpResponse response, string contents) { var buffer = Encoding.UTF8.GetBytes(contents); return response.Body.WriteAsync(buffer, 0, buffer.Length); } }
14、打個廣告:《ASP.NET Core框架揭祕》
ASP.NET Core Mini模擬了真實ASP.NET Core框架最核心的部分,即由伺服器和中介軟體構成的請求處理管道。真正的ASP.NET Core框架自然要複雜得多得多,那麼我們究竟遺漏了什麼呢?
如上所示的5個部分是ASP.NET Core Mini沒有涉及的,其中包括依賴注入、以Startup和StartupFilter的中介軟體註冊方式、針對多種資料來源的配置系統、診斷日誌系統和一系列預定義的中介軟體,上述的每個方面都涉及到一個龐大的主題,我們將ASP.NET Core涉及到的方方面都寫在我將要出版的《ASP.NET Core框架揭祕》中,如果你想全方面瞭解一個真實的ASP.NET Core框架,敬請期待新書出版。