ASP.NET Core 框架本质学习
本文作為學習過程中的一個記錄。
學習文章地址:
https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html
一. ASP.NET Core 框架上的 Hello World程序
public class Program{
public static void Main()
=> new WebHostBuilder()
.UseKestrel()
.Configure(app => app.Run(context => context.Response.WriteAsync("Hello World!")))
.Build()
.Run();
}
WebHost :承載Web應用的宿主;
WebHostBuilder :WebHost的構建者;
而 在WebHostBuilder在調用 Build方法之前,調用的 兩個方法:
UseKestrel :旨在注冊一個名為Kestrel的服務器
Configure:為了注冊一個用來處理請求的中間件
在上面的代碼中,中間件在響應的主體內容中寫入了一個 Hello World 的文本。
當我們在調用Run方法啟動作為應用宿主的 WebHost的時候,WebHost會利用WebHostBuilder提供的服務器和中間件構建一個請求處理管道。?
而下面主要講的就是 這個管道是如何被構建起來的,以及該管道采用怎樣的請求處理流程。
二. 在我們自己的ASP.NET Core Mini上面開發的 Hello World
本文作為學習過程中的一個記錄。
學習文章地址:
https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html
一. ASP.NET Core 框架上的 Hello World程序
public class Program{
public static void Main()
=> new WebHostBuilder()
.UseKestrel()
.Configure(app => app.Run(context => context.Response.WriteAsync("Hello World!")))
.Build()
.Run();
}
WebHost :承載Web應用的宿主;
WebHostBuilder :WebHost的構建者;
而 在WebHostBuilder在調用 Build方法之前,調用的 兩個方法:
UseKestrel :旨在注冊一個名為Kestrel的服務器
Configure:為了注冊一個用來處理請求的中間件
在上面的代碼中,中間件在響應的主體內容中寫入了一個 Hello World 的文本。
當我們在調用Run方法啟動作為應用宿主的 WebHost的時候,WebHost會利用WebHostBuilder提供的服務器和中間件構建一個請求處理管道。?
而下面主要講的就是 這個管道是如何被構建起來的,以及該管道采用怎樣的請求處理流程。
二. 在我們自己的ASP.NET Core Mini上面開發的 Hello World
代碼說明:
在創建出 WebHostBuilder 之后,我們調用了它的擴展方法 UseHttpListener 注冊了一個自定義的基于 HttpListener的服務器;
隨后針對 Configure 方法的調用中,我們注冊了三個中間件。
由于中間件最終是通過 Delegate對象來體現的,所以我們可以將中間件定義成與Delegate類型具有相同簽名的方法。
程序運行后,得到的輸出結果:
三. 自定義的ASP.NET Core Mini框架講解
下面主要是對 ASP.NET Core Mini框架的構建過程中關鍵部分的講解。
主要涉及 HttpContext、RequestDelegate、Middleware、ApplicationBuilder、Server、WebHost、WebHostBuilder 等七個對象;
另外 會講到 HttpContext與Server之間的適配;HttpListenerServer等;
1. 第一個對象:HttpContext
關于 HttpContext的本質,還得從請求處理管道的層面來講。
對于由一個服務器和多個中間件構建的管道來說,面向傳輸層的服務器負責請求的監聽、接收和最終的響應;
當它接收到客戶端發送的請求后,需要將它分發給后續中間件進行處理。
對于某個中間件來說,當我們完成了自身的請求處理任務之后,在大部分情況下,也需要將請求分發給后續的中間件。
請求在服務器與中間件之間,以及在中間件之間的分發是通過共享上下文的方式實現的。
( 如上圖,當服務器接收到請求之后,會創建一個通過HttpContext表示的上下文對象,所有中間件都是在這個上下文中處理請求的;
那么一個HttpContext對象究竟攜帶了怎樣的上下文信息呢?
我們知道一個HTTP事務具有非常清晰的界定,即接收請求、發送響應;
所以請求和響應是兩個基本的要素,也是HttpContext承載的最核心的 上下文信息。)
故,HttpContext的核心要素:請求和響應
2. 第二個對象:RequestDelegate
這是一個委托,也需要從管道的角度才能充分理解這個委托對象的本質。
?2.1 管道的設計
可以總結為?Pipeline = Server + Middlewares? ,再精簡寫的話,可以寫為?Pipeline = Server + HttpHandler .?
2.2 那么,我們如何來表達HttpHandler呢?
既然針對當前請求的所有輸入和輸出都通過HttpContext來表示,那么 HttpHandler就可以表示成一個 Action<HttpContext>對象。
那么HttpHandler在ASP.NET Core中時通過 Action<HttpContext>來表示的嗎?
其實不是的,原因很簡單:Action<HttpContext>只能表示針對請求的?同步的處理操作,但是針對 HTTP 請求既可以是同步的,也可以是異步的,更多的其實是異步的。
那么在 .NET Core的世界中如何來表示一個同步或者異步操作呢?就是Task對象,那么 HttpHandler自然可以表示為一個 Func<HttpContext,Task>對象。
由于這個委托對象實在太重要了,所以我們將它定義成一個獨立的類型:delegate Task RequestDelegate(HttpContext context)?。
3. 第三個對象:Middleware
中間件在ASP.NET Core 中被表示成一個 Func<RequestDelegate,RequestDelegate>對象,即它的輸入和輸出都是一個RequestDelegate。
為什么采用一個Func<RequestDelegate,RequestDelegate>對象來表示中間件。是因為這樣的考慮:
對于管道中的某一個中間件來說,由后續中間件組成的管道體現為一個RequestDelegate對象,由于當前中間件在完成了自身的請求處理任務之后,往往需要將請求分發給后續中間件進行處理,所以它需要將由后續中間件構成的RequestDelegate作為輸入。
即:上一個中間件的輸出需要可以作為下一個中間件的輸入,所以設計為Func<RequestDelegate,RequestDelegate>對象
4. 第四個對象:ApplicationBuilder
ApplicationBuilder 是用來構建 Application的。
既然 Pipeline = Server + HttpHandler , 可以看出HttpHandler承載了當前應用的所有職責,那么 HttpHandler就等于 Application。
由于 HttpHandler通過RequestDelegate表示,那么由ApplicationBuilder構建的Application就是一個RequestDelegate對象。(職責1)
由于表示HttpHandler的RequestDelegate是由注冊的中間件來構建的,所以ApplicationBuilder還具有注冊中間件的功能。(職責2)
基于ApplicationBuilder具有的這兩個基本職責,我們可以將對應的接口定義為如下形式。
Use 方法用來注冊提供的中間件,Builder方法則將注冊的中間件構建成一個RequestDelegate對象。
public interface IApplicationBuilder{
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
RequestDelegate Build();
}
下面是針對這個接口的具體實現。
我們用一個列表保存注冊的中間件,所以Use方法只需要將提供的中間件添加到這個列表中即可。
當Build方法被調用后,我們只需要按照與注冊相反的順序依次執行表示中間件的Func<RequestDelegate,RequestDelegate>對象,就能最終構建出代表HttpHandler的RequestDelegate對象。
在調用第一個中間件(最后注冊)的時候,我們創建了一個RequestDelegate作為輸入,后者會將響應狀態碼設置為404。
所以如果ASP.NET Core應用在沒有注冊任何中間件的情況下,總是返回一個404響應。
如果所有中間件在完成了自身的請求處理任務之后都選擇將請求向后分發,同樣會返回一個404響應。
總結:對于上面的四個對象,從后向前依次對前一個進行包裝。
5. 第五個對象:Server
當我們運行(Run)作為應用宿主的WebHost的時候,服務器它被自動啟動。
啟動后的服務器會綁定到指定的端口進行請求監聽,一旦有請求抵達,服務器會根據該請求創建出代表上下文的HttpContext對象,
并將該上下文作為輸入,調用由所有注冊中間件構建而成的RequestDelegate對象。
簡單起見,我們使用如下簡寫的IServer接口來表示服務器。
我們通過定義在IServer接口的唯一方法 StartAsync啟動服務器,
作為參數的?handler 正是由所有中間件共同構建而成的RequestDelegate對象
public interface IServer{
Task StartAsync(RequestDelegate handler);
}
6. HttpContext和Server之間的適配
面向應用層的HttpContext對象是對請求和相應的封裝,但是請求最初來源于服務器,針對HttpContext的任何響應操作也必須作用于當前的服務器才能真正起作用。
現在問題來了,所有的ASP.NET Core應用使用的都是同一個HttpContext類型,但是卻可以注冊不同類型的服務器,我們必須解決兩者之間的適配問題。
同一個HttpContext類型與不同服務器類型之間的適配可以通過添加一個抽象層來解決,我們定義該層的對象為Feature。
如上圖,我們可以定義一系列的Feature接口來為HttpContext提供上下文信息,其中最重要的就是提供請求的 IRequestFeature和完成響應的IResponseFeature接口。
那么具體的服務器只需要實現這些Feature接口就可以了。
下面是一些代碼片段。我們定義了一個IFeatureCollection接口來表示存放Feature對象的集合。
為了編程上的便利,我們定義了兩個擴展方法 Set<T>和Get<T>來設置和獲取Feature對象。
如下,用來提供請求的IHttpRequestFeature和提供響應IHttpResponseFeature接口的定義,可以看出它們具有和HttpRequest和HttpResponse完全一致的成員定義。
接下來,我們來看看HttpContext的具體實現。
ASP.NET Core Mini的HttpContext只包含Request和Response兩個屬性成員,對應的類型分別為HttpRequest和HttpResponse,下面是這兩個類型的具體實現。
其中,HttpRequest和HttpResponse都是通過一個IFeatureCollection對象構建而成的,它們對應的屬性成員均由包含在這個Feature集合中的IHttpRequestFeature和IHttpResponseFeature對象來提供。
HttpContext的實現就更加簡單了。我們在創建一個HttpContext對象時同樣會提供一個IFeatureCollection對象,
我們利用該對象創建對應的HttpRequest和HttpResponse對象,并作為其對應的屬性值。
總結:在HttpContext中傳入 IFeatureCollection對象,HttpContext中的成員對象HttpRequest和HttpResponse會利用這個IFeatureCollection來被構建。從而完成HttpContext的構建。當然,其中少不了需要Server部分,下面會講。
7. HttpListenerServer 服務器
在對服務器和它與HttpContext的適配原理有清晰的認識之后,我們嘗試著定義一個服務器。
在前面,我們利用WebHostBuilder的擴展方法UseHttpListener注冊了一個HttpListenerServer,我們現在來看看這個采用HttpListener作為監聽器的服務器類型是如何實現的。
由于所有的服務器都需要有自己的Feature實現來為HttpContext提供對應的上下文信息,所以我們得先來為HttpListenerServer定義相應的接口。
對HttpListener監聽器稍微了解的朋友應該知道它在接收到請求之后同時會創建一個自己的上下文對象,對應的類型為HttpListenerContext。
如果采用HttpListenerServer作為應用的服務器,意味著HttpContext承載的上下文信息最初來源于這個HttpListenerContext,所以Feature的目的旨在解決這兩個上下文之間的適配問題。
總結:Feature實際上解決的就是HttpContext和HttpListenerContext之間的適配問題。
下面的HttpListenFeature就是我們為HttpListenerServer定義的Feature。HttpListenerFeature同時實現了IHttpRequestFeature和IHttpResponseFeature,實現的 6 個屬性最初都來源于創建該對象提供的HttpListenerContext對象。
當HttpListener監聽到抵達的請求后,我們會得到一個HttpListenerContext對象,此時我們只需要據此創建一個HttpListenerFeature對象,
并且它分別以IHttpRequestFeature和IHttpResponseFeature接口類型注冊到創建FeatureCollection集合上。
我們最終利用這個FeatureCollection對象創建出代表上下文的HttpContext,然后將它作為參數調用由所有中間件公共構建的RequestDelegate對象即可。
8. 第六個對象:WebHost
到目前為止,我們已經知道了由一個服務器和多個中間件構成的管道是如何完整針對請求的監聽、接收、處理和最終響應的,接下來討論這樣的管道是如何被構建出來的。
管道是在作為應用宿主的WebHost對象啟動的時候被構建出來的,在ASP.NET Core Mini 中,
我們將表示應用宿主的IWebHost接口簡寫成如下形式:
只包含一個StartAsync方法來啟動應用程序。
public interface IWebHost{
Task StartAsync();
}
由于由WebHost構建的管道由Server和HttpHandler構成,我們在默認實現的WebHost類型中,我們直接提供這兩個對象。
在實現的StartAsync方法中,我們只需要將后者作為參數調用前者的StartAsync方法將服務器啟
9. 第七個對象:WebHostBuilder
WebHost的作用:就是創建作為應用宿主的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對象。
至此,本篇結束。
補充:
這里補充的是按照這里的學習,實現這個程序的過程。
1. 首先 創建 Feature.cs 文件
里面定義了請求和響應里面需要的一些內容。
2. 創建?FeatureCollection.cs 文件
這個應該就是 用來裝 請求和響應 的。
注:1,2兩個文件中的接口或者類,不依賴其他自定義類
3. 創建?HttpContext.cs 文件
這個里面定義了 請求實體類,響應實體類,共享上下文(HttpContext), 以及響應的擴展方法輸出內容。
4.創建?RequestDelegate.cs 文件
這個用來承載 共享上下文 HttpContext 的。
5. 創建?ApplicationBuilder.cs 文件
這個是用來承載 RequestDelegate 的,并且存放中間件及使之串起來的。
6. 創建?IServer.cs 文件
這個是用來定義服務器接口的。
7.創建?HttpListenerFeature.cs 文件
我們的共享上下文信息最初來源于這個類中的 HttpListenerContext。它是在服務器中,用來接收 HttpListener 中的上下文對象的(即HttpListenerContext)。
這里把 它按 請求和響應的接口定義進行拆分。
8. 創建?HttpListenerServer.cs 文件
這個是服務器文件,其中定義了監聽器,監聽的url集合,及啟動監聽,創建共享上下文,及使用RequestDelegate類型的處理器承載 上下文等操作。
9. 創建?WebHost.cs 文件
這里是用來創建宿主的,用來把服務器和中間件服務連接起來的。
10. 創建?WebHostBuilder.cs 文件
這個是宿主構建者,用來設置服務器,配置中間件,以及 Build 出宿主WebHost的。
11. 最后,創建?Program.cs 文件
這其中包括 創建宿主構建者,設置服務器,配置中間件, Build成宿主,及啟動宿主等。
代碼結束!!
12. 上面的代碼實際已經結束了,但是發現編譯的時候報錯。
C# 7.0 不支持Program.cs中的用法。
怎么修改呢?
右鍵項目---> 屬性----> 生成 ----> 高級 ---->?
然后在 常規 下的語言版本中,選擇 c#最新次要版本?
如下
13. 重新編譯,成功
14. 運行,然后在 瀏覽器中 輸入?http://localhost:5000
可以在代碼中打上斷點,觀察執行過程。
這里,把我自己寫的也上傳到github了,方便自己查閱,有疑問的小伙伴可以自己去原文學習
我的github地址:https://github.com/Vincent-yuan/asp.net_core_mini
原文鏈接:https://www.cnblogs.com/Vincent-yuan/p/11318718.html
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?
總結
以上是生活随笔為你收集整理的ASP.NET Core 框架本质学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SonarQube系列一、Linux安装
- 下一篇: 【活动】厦门.NET俱乐部 省上云开发者