200行代碼,7個對象——讓你了解ASP.NET Core框架的本質


2019年1月19日,微軟技術(蘇州)俱樂部成立,我受邀在成立大會上作了一個名為《ASP.NET Core框架揭秘》的分享。在此次分享中,我按照ASP.NET Core自身的運行原理和設計思想創建了一個 “迷你版” 的ASP.NET Core框架,並且利用這個 “極簡” 的模擬框架闡述了ASP.NET Core框架最核心、最本質的東西。整個框架涉及到的核心代碼不會超過200行,涉及到7個核心的對象。

PPT下載
源代碼下載

目錄
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程序兩個核心對象WebHostWebHostBuilder。我們可以將WebHost理解為寄宿或者承載Web應用的宿主,應用的啟動可以通過啟動作為宿主的WebHost來實現。至於WebHostBuilder,顧名思義,就是WebHost的構建者。

在調用WebHostBuilder的Build方法創建出WebHost之前,我們調用了它的兩個方法,其中UseKestrel旨在注冊一個名為Kestrel的服務器,而Configure方法的調用則是為了注冊一個用來處理請求的中間件,后者在響應的主體內容中寫入一個“Hello World”文本。

當我們調用Run方法啟動作為應用宿主的WebHost的時候,后者會利用WebHostBuilder提供的服務器和中間件構建一個請求處理管道。這個由一個服務器和若干中間件構成的管道就是ASP.NET Core框架的核心,我們接下來的核心任務就是讓大家搞清楚這個管道是如何被構建起來的,以及該管道采用怎樣的請求處理流程。

clip_image002[6]

2、ASP.NET Core Mini

在過去這些年中,我不斷地被問到同一個問題:如何深入地去一個開發框架。我知道每個人都具有適合自己的學習方式,而且我覺得我個人的學習方法也算不上高效,所以我很少會正面回應這個問題。不過有一個方法我倒很樂意與大家分享,那就是當你在學習一個開發框架的時候不要只關注編程層面的東西,而應該將更多的精力集中到對架構設計層面的學習。

針對某個框架來說,它提供的編程模式紛繁復雜,而底層的設計原理倒顯得簡單明了。那么如何檢驗我們對框架的設計原理是否透徹呢,我覺得最好的方式就是根據你的理解對框架進行“再造”。當你按照你的方式對框架進行“重建”的過程中,你會發現很多遺漏的東西。如果被你重建的框架能夠支撐一個可以運行的Hello World應用,那么可以基本上證明你已經基本理解了這個框架最本質的東西。

雖然ASP.NET Core目前是一個開源的項目,我們可以完全通過源碼來學習它,但是我相信這對於絕大部分人來說是有難度的。為此我們將ASP.NET Core最本質、最核心的部分提取出來,重新構建了一個迷你版的ASP.NET Core框架。

clip_image004[6]

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類型具有相同簽名的方法。

我們目前可以先不用考慮表示中間件的三個方法為什么需要成如上的形式,只需要知道三個中間件在針對請求的處理流程中都作了些什么。上面的代碼很清楚,三個中間件分別會在響應的內容中寫入一段文字,所以程序運行后,如果我們利用瀏覽器訪問該應用,會得到如下所示的輸出結果。

clip_image006[6]

4、第一個對象:HttpContext

正如本篇文章表示所說,我們的ASP.NET Core Mini由7個核心對象構建而成。第一個就是大家非常熟悉的HttpContext對象,它可以說是ASP.NET Core應用開發中使用頻率最高的對象。要說明HttpContext的本質,還得從請求處理管道的層面來講。對於由一個服務器和多個中間件構建的管道來說,面向傳輸層的服務器負責請求的監聽、接收和最終的響應,當它接收到客戶端發送的請求后,需要將它分發給后續中間件進行處理。對於某個中間件來說,當我們完成了自身的請求處理任務之后,在大部分情況下也需要將請求分發給后續的中間件。請求在服務器與中間件之間,以及在中間件之間的分發是通過共享上下文的方式實現的。

clip_image008[6]

如上圖所示,當服務器接收到請求之后,會創建一個通過HttpContext表示的上下文對象,所有中間件都是在這個上下文中處理請求的,那么一個HttpContext對象究竟攜帶怎樣的上下文信息呢?我們知道一個HTTP事務(Transaction)具有非常清晰的界定,即接收請求、發送響應,所以請求響應是兩個基本的要素,也是HttpContext承載的最核心的上下文信息。

我們可以將請求理解為輸入、響應理解為輸出,所以應用程序可以利用HttpContext得到當前請求所有的輸入信息,也可以利用它完成我們所需的所有輸出工作。為此我們為ASP.NET Core Mini定義了如下這個極簡版本的HttpContext。

public class HttpContext
{           
    public  HttpRequest Request { get; }
    public  HttpResponse Response { get; }
}
public class HttpRequest
{
    public  Uri Url { get; }
    public  NameValueCollection Headers { get; }
    public  Stream Body { get; }
}
public class HttpResponse
{
    public  NameValueCollection Headers { get; }
    public  Stream 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

clip_image010[6]

那么我們如來表達HttpHandler呢?我們可以這樣想:既然針對當前請求的所有輸入和輸出都通過HttpContext來表示,那么HttpHandler就可以表示成一個Action<HttpContext>對象。那么HttpHandler在ASP.NET Core中是通過Action<HttpContext>來表示的嗎?其實不是的,原因很簡單:Action<HttpContext>只能表示針對請求的 “同步” 處理操作,但是針對HTTP請求既可以是同步的,也可以是異步的,更多地其實是異步的。

那么在.NET Core的世界中如何來表示一個同步或者異步操作呢?你應該想得到,那就是Task對象,那么HttpHandler自然就可以表示為一個Func<HttpContext,Task>對象。由於這個委托對象實在太重要了,所以我們將它定義成一個獨立的類型。

clip_image012[6]

6、第三個對象:Middleware

在對RequestDelegate這個委托對象具有充分認識之后,我們來聊聊中間件又如何表達,這也是我們介紹的第三個核心對象。中間件在ASP.NET Core被表示成一個Func<RequestDelegate, RequestDelegate>對象,也就是說它的輸入和輸出都是一個RequestDelegate

clip_image014[6]

對於為什么會采用一個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對象。

clip_image016[6]

由於表示HttpHandler的RequestDelegate是由注冊的中間件來構建的,所以ApplicationBuilder還具有注冊中間件的功能。基於ApplicationBuilder具有的這兩個基本職責,我們可以將對應的接口定義成如下的形式。Use方法用來注冊提供的中間件,Build方法則將注冊的中間件構建成一個RequestDelegate對象。

public interface  IApplicationBuilder
{
    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()
    {
        _middlewares.Reverse();
        return httpContext =>
        {
            RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; };
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對象。

clip_image018[6]

簡單起見,我們使用如下這個簡寫的IServer接口來表示服務器。我們通過定義在IServer接口的唯一方法StartAsync啟動服務器,作為參數的handler正是由所有注冊中間件共同構建而成的RequestDelegate對象

public interface IServer
{ 
    Task StartAsync(RequestDelegate handler);
}

9、HttpContext和Server之間的適配

面向應用層的HttpContext對象是對請求和響應的封裝,但是請求最初來源於服務器,針對HttpContext的任何響應操作也必需作用於當前的服務器才能真正起作用。現在問題來了,所有的ASP.NET Core應用使用的都是同一個HttpContext類型,但是卻可以注冊不同類型的服務器,我們必需解決兩者之間的適配問題。

clip_image020[6]

計算機領域有一句非常經典的話:“任何問題都可以通過添加一個抽象層的方式來解決,如果解決不了,那就再加一層”。同一個HttpContext類型與不同服務器類型之間的適配問題也可可以通過添加一個抽象層來解決,我們定義在該層的對象稱為Feature。如上圖所示,我們可以定義一系列的Feature接口來為HttpContext提供上下文信息,其中最重要的就是提供請求的IRequestFeature和完成響應的IResponseFeature接口。那么具體的服務器只需要實現這些Feature接口就可以了。

clip_image022[6]

我們接着從代碼層面來看看具體的實現。如下面的代碼片段所示,我們定義了一個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
{
    Uri                     Url { get; }
    NameValueCollection     Headers { get; }
    Stream                  Body { get; }
}    
public interface IHttpResponseFeature
{
    int                       StatusCode { get; set; }
    NameValueCollection     Headers { get; }
    Stream                  Body { get; }
}

接下來我們來看看HttpContext的具體實現。ASP.NET Core Mini的HttpContext只包含Request和Response兩個屬性成員,對應的類型分別為HttpRequest和HttpResponse,如下所示的就是這兩個類型的具體實現。我們可以看出HttpRequest和HttpResponse都是通過一個IFeatureCollection對象構建而成的,它們對應的屬性成員均有分別由包含在這個Feature集合中的IHttpRequestFeature和IHttpResponseFeature對象來提供的。

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>();

}

HttpContext的實現就更加簡單了。如下面的代碼片段所示,我們在創建一個HttpContext對象是同樣會提供一個IFeatureCollection對象,我們利用該對象創建對應的HttpRequest和HttpResponse對象,並作為對應的屬性值。

public class HttpContext
{           
    public  HttpRequest Request { get; }
    public  HttpResponse 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的目的旨在解決這兩個上下文之間的適配問題。

clip_image024[6]

如下所示的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框架自然要復雜得多得多,那么我們究竟遺漏了什么呢?

clip_image026[6]

如上所示的5個部分是ASP.NET Core Mini沒有涉及的,其中包括依賴注入、以Startup和StartupFilter的中間件注冊方式、針對多種數據源的配置系統、診斷日志系統和一系列預定義的中間件,上述的每個方面都涉及到一個龐大的主題,我們將ASP.NET Core涉及到的方方面都寫在我將要出版的《ASP.NET Core框架揭秘》中,如果你想全方面了解一個真實的ASP.NET Core框架,敬請期待新書出版。

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM