ASP.NET Core 框架本質學習

yuan發表於2019-08-12

本文作為學習過程中的一個記錄。

學習文章地址:

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

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 註冊了一個自定義的基於 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的核心要素:請求和響應

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;}  //響應狀態碼
}

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物件。

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響應。

 

總結:對於上面的四個物件,從後向前依次對前一個進行包裝。

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物件。

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

 

總結:在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物件。

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; }
}

當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方法將伺服器啟動就可以

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

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物件。

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

至此,本篇結束。

 

補充:

這裡補充的是按照這裡的學習,實現這個程式的過程。

1. 首先 建立 Feature.cs 檔案

 1 namespace App
 2 {
 3     public interface IHttpRequestFeature
 4     {
 5         Uri Url { get; }
 6 
 7         NameValueCollection Headers { get; }
 8 
 9         Stream Body { get; }
10     }
11 
12 
13     public interface IHttpResponseFeature
14     {
15         int StatusCode { get; set; }
16 
17         NameValueCollection Headers { get; }
18 
19         Stream Body { get; }
20 
21     }
View Code

裡面定義了請求和響應裡面需要的一些內容。

2. 建立 FeatureCollection.cs 檔案

 1 namespace App
 2 {
 3     public interface IFeatureCollection : IDictionary<Type, object> { }
 4 
 5     public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection { }
 6 
 7     public static partial class Extensions
 8     {
 9         public static T Get<T>(this IFeatureCollection features) => features.TryGetValue(typeof(T), out var value) ? (T)value : default(T);
10 
11         public static IFeatureCollection Set<T>(this IFeatureCollection features,T feature)
12         {
13             features[typeof(T)] = feature;
14             return features;
15         }
16     }
17 }
View Code

這個應該就是 用來裝 請求和響應 的。

注:1,2兩個檔案中的介面或者類,不依賴其他自定義類

3. 建立 HttpContext.cs 檔案

 1 namespace App
 2 {
 3     /// <summary>
 4     /// 共享上下文
 5     /// </summary>
 6     public class HttpContext
 7     {
 8         public HttpRequest Request { get; }
 9 
10         public HttpResponse Response { get; }
11 
12         public HttpContext(IFeatureCollection features)
13         {
14             Request = new HttpRequest(features);
15             Response = new HttpResponse(features);
16         }
17     }
18 
19     /// <summary>
20     /// 表示請求實體
21     /// 會使用到IFeatureCollection介面
22     /// </summary>
23     public class HttpRequest
24     {
25         private readonly IHttpRequestFeature _feature;
26 
27         public HttpRequest(IFeatureCollection features) => _feature = features.Get<IHttpRequestFeature>();
28 
29         public Uri URl => _feature.Url;
30         public NameValueCollection Headers => _feature.Headers;
31 
32         public Stream Body => _feature.Body;
33 
34        
35     }
36 
37     /// <summary>
38     /// 響應實體
39     /// 需要用到IFeatureCollection介面
40     /// </summary>
41     public class HttpResponse
42     {
43         private readonly IHttpResponseFeature _feature;
44 
45         public HttpResponse(IFeatureCollection features) => _feature = features.Get<IHttpResponseFeature>();
46 
47         public int StatusCode
48         {
49             get => _feature.StatusCode;
50             set => _feature.StatusCode = value;
51         }
52 
53         public NameValueCollection Headers => _feature.Headers;
54 
55         public Stream Body => _feature.Body;
56     }
57 
58     /// <summary>
59     /// 響應的輸出
60     /// </summary>
61     public static partial class Extensions
62     {
63         public static Task WriteAsync(this HttpResponse response,string contents)
64         {
65             var buffer = Encoding.UTF8.GetBytes(contents);
66             return response.Body.WriteAsync(buffer, 0, buffer.Length);
67         }
68     }
69 
70 }
View Code

這個裡面定義了 請求實體類,響應實體類,共享上下文(HttpContext), 以及響應的擴充套件方法輸出內容。

4.建立 RequestDelegate.cs 檔案

1 namespace App
2 {
3     public delegate Task RequestDelegate(HttpContext context);
4 }
View Code

這個用來承載 共享上下文 HttpContext 的。

5. 建立 ApplicationBuilder.cs 檔案

 1 namespace App
 2 {
 3     /// <summary>
 4     /// Application構建者
 5     /// </summary>
 6     public interface IApplicationBuilder
 7     {
 8         IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
 9 
10         RequestDelegate Build();
11     }
12 
13     public class ApplicationBuilder : IApplicationBuilder
14     {
15         /// <summary>
16         /// 用來存放註冊的中介軟體
17         /// </summary>
18         private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();
19 
20         public RequestDelegate Build()
21         {
22             _middlewares.Reverse();
23             return httpContext =>
24             {
25                 RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; };
26 
27                 foreach(var middleware in _middlewares)
28                 {
29                     next = middleware(next);
30                 }
31                 return next(httpContext);
32             };
33         }
34 
35         public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
36         {
37             _middlewares.Add(middleware);
38             return this;
39         }
40     }
41 }
View Code

這個是用來承載 RequestDelegate 的,並且存放中介軟體及使之串起來的。

6. 建立 IServer.cs 檔案

1 namespace App
2 {
3     public interface IServer
4     {
5         Task StartAsync(RequestDelegate handler);
6     }
7 }
View Code

這個是用來定義伺服器介面的。

7.建立 HttpListenerFeature.cs 檔案

 1 namespace App
 2 {
 3     public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature
 4     {
 5         private readonly HttpListenerContext _context;
 6 
 7         public HttpListenerFeature(HttpListenerContext context) => _context = context;
 8 
 9         Uri IHttpRequestFeature.Url => _context.Request.Url;
10 
11         NameValueCollection IHttpRequestFeature.Headers => _context.Request.Headers;
12 
13         NameValueCollection IHttpResponseFeature.Headers => _context.Response.Headers;
14 
15         Stream IHttpRequestFeature.Body => _context.Request.InputStream;
16 
17         Stream IHttpResponseFeature.Body => _context.Response.OutputStream;
18 
19         int IHttpResponseFeature.StatusCode
20         {
21             get { return _context.Response.StatusCode; }
22             set { _context.Response.StatusCode = value; }
23         }
24     }
25 }
View Code

我們的共享上下文資訊最初來源於這個類中的 HttpListenerContext。它是在伺服器中,用來接收 HttpListener 中的上下文物件的(即HttpListenerContext)。

這裡把 它按 請求和響應的介面定義進行拆分。

8. 建立 HttpListenerServer.cs 檔案

 1 namespace App
 2 {
 3     public class HttpListenerServer : IServer
 4     {
 5         //監聽器
 6         private readonly HttpListener _httpListener;
 7         //監聽的url集合
 8         private readonly string[] _urls;
 9 
10         public HttpListenerServer(params string[] urls)
11         {
12             _httpListener = new HttpListener();
13             _urls = urls.Any() ? urls : new string[] { "http://localhost:5000/" }; 
14         }
15 
16         /// <summary>
17         /// 啟動伺服器
18         /// </summary>
19         /// <param name="handler"></param>
20         /// <returns></returns>
21         public async Task StartAsync(RequestDelegate handler)
22         {
23             #region 啟動監聽
24             Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));
25             _httpListener.Start();
26             Console.WriteLine("Server started and is listening on:{0}", string.Join(';', _urls));
27             #endregion
28 
29             while (true)
30             {
31                 //獲取監聽的上下文
32                 var listenerContext = await _httpListener.GetContextAsync();
33 
34 
35                 var feature = new HttpListenerFeature(listenerContext);
36                 var features = new FeatureCollection()
37                                 .Set<IHttpRequestFeature>(feature)
38                                 .Set<IHttpResponseFeature>(feature);
39                 var httpContext = new HttpContext(features);
40 
41                 await handler(httpContext);
42                 listenerContext.Response.Close();
43             }
44 
45 
46         }
47     }
48 
49     public static partial class Extensions
50     {
51         /// <summary>
52         /// IWebHostBuilder使用伺服器的擴充套件方法
53         /// </summary>
54         /// <param name="builder"></param>
55         /// <param name="urls"></param>
56         /// <returns></returns>
57         public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder, params string[] urls)
58         => builder.UseServer(new HttpListenerServer(urls));
59     }
60 }
View Code

這個是伺服器檔案,其中定義了監聽器,監聽的url集合,及啟動監聽,建立共享上下文,及使用RequestDelegate型別的處理器承載 上下文等操作。

9. 建立 WebHost.cs 檔案

 1 namespace App
 2 {
 3     public interface IWebHost
 4     {
 5         Task StartAsync();
 6     }
 7 
 8     /// <summary>
 9     /// 宿主
10     /// </summary>
11     public class WebHost : IWebHost
12     {
13         private readonly IServer _server;
14         private readonly RequestDelegate _handler;
15 
16         public WebHost(IServer server,RequestDelegate handler)
17         {
18             _server = server;
19             _handler = handler;
20         }
21 
22         public Task StartAsync() => _server.StartAsync(_handler);
23     }
24 }
View Code

這裡是用來建立宿主的,用來把伺服器和中介軟體服務連線起來的。

10. 建立 WebHostBuilder.cs 檔案

 1 namespace App
 2 {
 3     public interface IWebHostBuilder
 4     {
 5         IWebHostBuilder UseServer(IServer server);
 6         IWebHostBuilder Configure(Action<IApplicationBuilder> configure);
 7 
 8         IWebHost Build();
 9 
10     }
11 
12     public class WebHostBuilder : IWebHostBuilder
13     {
14         private IServer _server;
15         private readonly List<Action<IApplicationBuilder>> _configures = new List<Action<IApplicationBuilder>>();
16         
17 
18         public IWebHostBuilder Configure(Action<IApplicationBuilder> configure)
19         {
20             _configures.Add(configure);
21             return this;
22         }
23 
24         public IWebHostBuilder UseServer(IServer server)
25         {
26             _server = server;
27             return this;
28         }
29 
30         public IWebHost Build()
31         {
32             var builder = new ApplicationBuilder();
33             foreach(var configure in _configures)
34             {
35                 configure(builder);
36             }
37             return new WebHost(_server, builder.Build());
38         }
39 
40 
41     }
42 }
View Code

這個是宿主構建者,用來設定伺服器,配置中介軟體,以及 Build 出宿主WebHost的。

11. 最後,建立 Program.cs 檔案

 1 namespace App
 2 {
 3     public class Program
 4     {
 5 
 6         public static async Task Main()
 7         {
 8             await new WebHostBuilder()
 9                 .UseHttpListener()
10                 .Configure(app => app
11                     .Use(FooMiddleware)
12                     .Use(BarMiddleware)
13                     .Use(BazMiddleware))
14                 .Build()
15                 .StartAsync();
16         }
17 
18         public static RequestDelegate FooMiddleware(RequestDelegate next)
19             => async context =>
20              {
21                  await context.Response.WriteAsync("Foo=>");
22                  await next(context);
23              };
24 
25         public static RequestDelegate BarMiddleware(RequestDelegate next)
26             => async context =>
27              {
28                  await context.Response.WriteAsync("Bar=>");
29                  await next(context);
30              };
31 
32         public static RequestDelegate BazMiddleware(RequestDelegate next)
33         => async context =>
34          {
35              await context.Response.WriteAsync("Baz=>");
36          };
37     }
38 }
View Code

這其中包括 建立宿主構建者,設定伺服器,配置中介軟體, Build成宿主,及啟動宿主等。

程式碼結束!!

12. 上面的程式碼實際已經結束了,但是發現編譯的時候報錯。

C# 7.0 不支援Program.cs中的用法。

怎麼修改呢?

右鍵專案---> 屬性----> 生成 ----> 高階 ----> 

然後在 常規 下的語言版本中,選擇 c#最新次要版本 

如下

13. 重新編譯,成功

 

14. 執行,然後在 瀏覽器中 輸入 http://localhost:5000

可以在程式碼中打上斷點,觀察執行過程。

 

這裡,把我自己寫的也上傳到github了,方便自己查閱,有疑問的小夥伴可以自己去原文學習

我的github地址:https://github.com/Vincent-yuan/asp.net_core_mini

相關文章