歡迎光臨
每天分享高質量文章

200行代碼,7個物件——讓你瞭解ASP.NET Core框架的本質

作者:Artech

鏈接:https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html

 

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

 

PPT下載:https://files-cdn.cnblogs.com/files/artech/Inside-ASP-NET-Core-Framework.pdf

原始碼下載

目錄

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 Core Mini

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

 

針對某個框架來說,它提供的編程樣式紛繁複雜,而底層的設計原理倒顯得簡單明瞭。那麼如何檢驗我們對框架的設計原理是否透徹呢,我覺得最好的方式就是根據你的理解對框架進行“再造”。當你按照你的方式對框架進行“重建”的過程中,你會發現很多遺漏的東西。如果被你重建的框架能夠支撐一個可以運行的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
{           
    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 { getset;}
}

 

如上面的代碼片段所示,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物件。那麼HttpHandler在ASP.NET Core中是通過Action來表示的嗎?其實不是的,原因很簡單:Action只能表示針對請求的 “同步” 處理操作,但是針對HTTP請求既可以是同步的,也可以是異步的,更多地其實是異步的。

 

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

 

6、第三個物件:Middleware

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

 

 

對於為什麼會採用一個Func物件來表示中間件,很多初學者會很難理解。我們可以這樣的考慮:對於管道的中的某一個中間件來說,由後續中間件組成的管道體現為一個RequestDelegate物件,由於當前中間件在完成了自身的請求處理任務之後,往往需要將請求分發給後續中間件進行處理,所有它它需要將由後續中間件構成的RequestDelegate作為輸入。

 

當代表中間件的委托物件執行之後,我們希望的是將當前中間件“納入”這個管道,那麼新的管道體現的RequestDelegate自然成為了輸出結果。所以中間件自然就表示成輸入和輸出均為RequestDelegate的Func物件。

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 interface  IApplicationBuilder
{
    IApplicationBuilder Use(Func middleware);
    RequestDelegate Build();
}

 

如下所示的是針對該接口的具體實現。我們利用一個串列來儲存註冊的中間件,所以Use方法只需要將提供的中間件添加到這個串列中即可。當Build方法被呼叫之後,我們只需按照與註冊相反的順序依次執行表示中間件的Func物件就能最終構建出代表HttpHandler的RequestDelegate物件。

public class ApplicationBuilder : IApplicationBuilder
{
    private readonly List> _middlewares = new List>();
    public RequestDelegate Build()
    {
        _middlewares.Reverse();
        return httpContext =>
        {
            RequestDelegate next = _ => { _.Response.StatusCode = 404return Task.CompletedTask; };
            foreach (var middleware in _middlewares)
            {
                next = middleware(next);
            }
            return next(httpContext);
        };
    }

    public IApplicationBuilder Use(Func 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和Get來設置和獲取Feature物件。

public interface IFeatureCollection : IDictionary<Type, object{ }
public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection { }   
public static partial class Extensions
{
    public static T Get(this IFeatureCollection features) => features.TryGetValue(typeof(T), out var value) ? (T)value : default(T);
    public static IFeatureCollection Set(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 { getset; }
    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();
}

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

}

 

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的目的旨在解決這兩個背景關係之間的適配問題。

 

 

如下所示的HttpListenerFeature就是我們為HttpListenerServer定義的Feature。

HttpListenerFeature同時實現了IHttpRequestFeature和IHttpResponseFeature,實現的6個屬性成員最初都來源於創建該Feature物件提供的HttpListenerContext物件。

public class HttpListenerFeature : IHttpRequestFeatureIHttpResponseFeature
{
    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(feature)
                .Set(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 configure);
    IWebHost Build();
}

 

除了用來創建WebHost的Build方法之外,我們提供了用來註冊服務器的UseServer方法和用來註冊中間件的Configure方法。Configure方法提供了一個型別為Action的引數,意味著我們針對中間件的註冊是利用上面介紹的IApplicationBuilder物件來完成的。

 

如下所示的WebHostBuilder是針對IWebHostBuilder接口的預設實現,它具有兩個欄位分別用來儲存註冊的中間件和呼叫Configure方法提供的Action物件。當Build方法被呼叫之後,我們創建一個ApplicationBuilder物件,並將它作為引數呼叫這些Action委托,進而將所有中間件全部註冊到這個ApplicationBuilder物件上。我們最終呼叫它的Build方法得到由所有中間件共同構建的RequestDelegate物件,並利用它和註冊的服務器構建作為應用宿主的WebHost物件。

public class WebHostBuilder : IWebHostBuilder
{
    private IServer _server;
    private readonly List> _configures = new List>();   

    public IWebHostBuilder Configure(Action 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物件,我們自然可以採用與之具有相同宣告的方法(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框架,敬請期待新書出版。

 

赞(0)

分享創造快樂