ASP.NETCore學習記錄(二) —— ASP.NET Core 中間件

 ASP.NET Core 中間件


目錄:
html

 


咱們知道在 ASP.NET 中,有一個面向切面的請求管道,由22個主要的事件構成,可以讓咱們在往預約的執行順序裏面添加本身的處理邏輯。通常採起兩種方式:一種是直接在 Global.asax 中對應的方法中直接添加代碼。一種是是在 web.config 中經過註冊 HttpModule 來實現對請求管道事件監聽,並經過 HttpHandler 進入到咱們的應用程序中。而在 ASP.NET Core 中,對請求管道進行了從新設計,經過使用一種稱爲中間件的方式來進行管道的註冊,同時也變得更加簡潔和強大。關於 HTTP請求流程和管道模型能夠看這篇博客 http://www.javashuo.com/article/p-nfkrnuxg-u.htmlweb


什麼是中間件 ?

中間件 (Middleware)組裝到應用程序管道中以處理請求和響應的組件。 管道內的每一個組件均可以選擇是否將請求交給下一個組件,並在管道中調用下一個組件以前和以後執行某些操做。請求委託被用來創建請求管道,請求委託處理每個HTTP請求。跨域

請求委託經過使用 IApplicationBuilder 類型的 RunMap 以及 Use 擴展方法來配置,並在 Startup 類中傳給 Configure方法。每一個單獨的清求委託均可以被指定爲一個內嵌匿名方法,或其定義在一個可重用的類中。這些可重用的類被稱做 「中間件」或「中間件組件」。每一個位於請求管道內的中間件組件負責調用管道中的下一個組件,或適時短路調用鏈。數組

ASP.NET請求管道由一系列的請求委託所構成,它們一個接着一個被調用,如圖所示(該執行線程按黑色箭頭的順序執行)。瀏覽器

 

微軟官方的一箇中間件管道請求圖
微軟官方的一箇中間件管道請求圖

 

每一個委託:緩存

  • 都可在下一個委託先後執行操做
  • 委託還能夠決定是否將請求傳遞給下一個委託(任何委託都能選擇中止傳遞到下一個委託,轉而本身處理該請求,這就是請求管道的短路)。

短路是一種有意義的設計,由於它能夠避免沒必要要的工做。好比說:安全

  • 靜態文件中間件能夠返回靜態文件請求並使管道的其他部分短路。
  • 受權中間件只有經過身份驗證以後才能調用下一個中間件,不然就會被他短路。

先在管道中調用異常處理委託,以便它們能夠捕獲在管道的後期階段所發生的異常。服務器

返回頂部session


IApplicationBuilder

筆記一中,咱們就簡單介紹過 IApplicationBuilder,在 Startup 類的 Configure方法中,第一個參數即是 IApplicationBuilderapp

首先,IApplicationBuilder 是用來構建請求管道的,而所謂請求管道,本質上就是對 HttpContext 的一系列操做,即經過對 Request 的處理,來生成 Reponse。在 ASP.NET Core 中定義了一個 RequestDelegate 委託,來表示請求管道中的一個步驟,而對請求管道的註冊是經過 Func<RequestDelegate, RequestDelegate> 類型的委託(也就是中間件)來實現的。

返回頂部


使用 IApplicationBuilder 建立中間件

能夠先看一下 VisualStudio2017 中默認Web(MVC)站點模板關於請求管道設置的例子。

 


 

 

Startup.csConfigure 方法默認增長了下列這些中間件組件:

  • 異常/錯誤處理(同時針對開發環境和非開發環境)
  • Https重定向
  • 靜態文件服務器
  • Cookie 策略實施
  • MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
    if (env.IsDevelopment())
    {
        // 開發環境
        app.UseDeveloperExceptionPage(); 
    }
    else
    {	
        // 非開發環境
        app.UseExceptionHandler("/Home/Error"); 
        app.UseHsts();
    }

    app.UseHttpsRedirection();// Https重定向
    app.UseStaticFiles();   // 靜態文件
    app.UseCookiePolicy();  // 使用Cookie策略

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    }); // MVC
}

上面的代碼在非開發環境中,UseExceptionHandler 是第一個被加入到管道中的中間件,所以將會捕獲以後調用組件中出現的任何異常,而後跳轉到設置的異常頁(/Home/Error)。

使用 UseHttpsRedirection 中間件,能夠輕鬆實施HTTPS,將HTTP重定向到HTTPS(ASP.NET Core 2.1具備新功能)。

而後就是靜態文件中間件 UseStaticFiles,靜態文件中間件不提供受權檢查,由它提供的任何文件,包括那些位於wwwroot下的文件都是公開可被訪問的。UseCookiePolicy 是使用ASP.NET Core中的Cookie策略(詳解Microsoft.AspNetCore.CookiePolicy),管道的最後執行的也就是MVC框架。

順序
Startup.Configure 方法添加中間件組件的順序定義了針對請求調用這些組件的順序,以及響應的相反順序。 此排序對於安全性、性能和功能相當重要。請求在每一步均可能被短路,因此咱們要以正確的順序添加中間件,如異常處理,咱們須要添加在最開始,這樣咱們就能第一時間捕獲異常,以及後續中間可能發生的異常,而後最終作處理返回。

最簡單的ASP.NET應用程序(空白Web模板)是使用單個請求委託來處理全部請求。事實上,在這種狀況下,並不存在所謂的「管道」,針對每一個HTTP請求都調用單個匿名函數來處理。

public void Configure(IApplicationBuilder app) {
    // 使用單個請求委託來處理全部請求
    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
}

上方代碼中 app.Run() 會中斷管道,調用單個匿名函數來處理HTTP請求。在下面的例子中,Run了兩個委託,只有第一個委託(「Hello,World!」)會被運行。

public void Configure(IApplicationBuilder app) {
    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Welcome!");
    });
}

經過運行項目,發現確實只有在第一個委託執行了,app.Run 終止了管道。

 


 

 

你能夠將多個請求委託 app.Use 鏈接在一塊兒,next參數表示管道內的下一個請求委託。在管道中,能夠經過不調用next參數來終止或短路管道。一般能夠在執行下一個委託以前和以後執行一些操做,以下例所示:

public void Configure(IApplicationBuilder app) {
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Handing Request.\r\n");
        
        // 調用管道中的下一個委託
        await next.Invoke();
        await context.Response.WriteAsync("Finish Handing Request.\r\n");
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello from Middleware\r\n");
    });
}

運行項目,在瀏覽器中訪問出現以下結果:

 


 

 

返回頂部


Run、Map 與 Use 方法

你可使用 RunMapUse 方法配置 HTTP 管道。

Run 方法會短路管道,由於它不會調用 next 請求委託。所以 Run 方法通常只在管道底部被調用。Run 方法是一種慣例約定,有些中間件組件可能會暴露它們本身的 Run[Middleware]方法,而這些方法只能在管道末尾處運行。

Use 前面已經簡單介紹經過 Use 構建請求管道的例子,Use 方法亦可以使管道短路(即不調用 next 請求委託)。

Map 擴展方法用匹配基於請求路徑的請求委託,Map只接受路徑,並配置單獨的中間件管道的功能。 Map* 方法能夠基於給定請求路徑的匹配項來建立請求管道分支。 若是請求路徑以給定路徑開頭,則執行分支。以下面例子中:

public void Configure(IApplicationBuilder app) {
    app.Map("/map1", HandleMapTest1);
    app.Map("/map2", HandleMapTest2);
    app.Run(async context =>
    {
        await context.Response.WriteAsync("<p> Hello from non-Map delegate. </p>");
    });
}

private static void HandleMapTest1(IApplicationBuilder app) {
    app.Run(async context =>
    {
        await context.Response.WriteAsync("<p> Map Test 1 </p>");
    });
}

private static void HandleMapTest2(IApplicationBuilder app) {
    app.Run(async context =>
    {
        await context.Response.WriteAsync("<p> Map Test 2 </p>");
    });
}

任何基於路徑 /map1 的請求都會被管道中所配置 HandleMapTest1 方法處理。基於路徑 /map2 的請求都會被管道中所配置 HandleMapTest2 方法處理。

 


 

 

下表顯示上例來自 http://localhost:52831 的請求和響應。

請求 響應
http://localhost:52831 Hello from non-Map delegate.
http://localhost:52831/map1 Map Test 1
http://localhost:52831/map2 Map Test 2
http://localhost:52831/map3 Hello from non-Map delegate.

除了基於路徑的映射外,MapWhen 方法還支持基於謂語的中間件分支,容許以很是靈活的方式構建單獨的管道。任何 Func<HttpContext, bool> 類型的謂語均可以被用於將請求映射到新的管道分支。下例中使用了一個簡單的謂語來檢測查洵字符串中變量 branch 是否存在:

public class Startup {
    public void Configure(IApplicationBuilder app) {
        app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);
        app.Run(async context =>
        {
            await context.Response.WriteAsync("<p> Hello from non-Map delegate. </p>");
        });
    }

    private static void HandleBranch(IApplicationBuilder app) {
        app.Run(async context =>
        {
            var branchVer = context.Request.Query["branch"];
            await context.Response.WriteAsync($"Branch used = {branchVer}");
        });
    }
}

使用了上面的設置後,任何包含請求字符 branch 的請求將使用定義於 HandleBranch 方法內的管道,若是沒有包含查詢字符串 branch 的請求,將被 app.Run 所定義的委託處理。

 


 

 

請求 響應
http://localhost:52831 Hello from non-Map delegate.
http://localhost:52831/?branch=1 Branch used = 1
http://localhost:52831/?branch=2 Branch used = 2
http://localhost:52831/?branch Branch used =

另外還能夠嵌套 Maps:

app.Map("/level1", level1App => {
   level1App.Map("/level2a", level2AApp => {
       // "/level1/level2a"
       //...
   });
   level1App.Map("/level2b", level2BApp => {
       // "/level1/level2b"
       //...
   });
});

Map 也能夠一次匹配多個片斷,例如:

app.Map("/level1/level2", HandleMultiSeg); // "/level1/level2"

如下 Startup.Configure 方法將爲常見應用方案添加中間件組件:

中間件 描述
身份驗證(Authentication) 提供身份驗證支持
跨域資源共享(CORS) 配置跨域資源共享
響應支持(Response Caching) 提供緩存響應支持
響應壓縮(Response Compression) 提供響應壓縮支持
路由(Routing) 定義和約束請求路由
會話(Session) 提供對管理用戶會話(session)的支持
靜態文件(Static Files) 爲靜態文件和目錄瀏覽提供服務提供支持
URL Rewriting Middleware 用於重寫 Url,並將請求重定向的支持

更多中間件組件能夠到 Microsoft 文檔 上查看。

返回頂部


實戰中間件

如何實現一箇中間件呢,下面咱們來實際操做。
中間件遵循 顯式依賴原則 ,並在其構造函數中暴露全部的依賴項。中間件可以利用 UseMiddleware<T> 擴展方法的優點,直接經過它們的構造函數注入服務。依賴注入服務是自動完成填充的,擴展所用到的 params 參數數組被用於非注入參數。

下面來實現一個記錄IP的中間件。

① 新建一個 ASP.NET Core WebApplication 項目,選擇空的模板。

 


 

 

而後爲項目添加—個 Microsoft.Extensions.Logging.Console。
NuGet命令行執行(請使用官方源):

Install-Package Microsoft.Extensions.Logging.Console

 


 

 

② 新建一個類 RequestIPMiddleware.es,將中間件委託移動到類:

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace middlewareDemo
{
    public class RequestIPMiddleware {
        private readonly RequestDelegate _next;

        private readonly ILogger _logger;

        public RequestIPMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) {
            _next = next;
            _logger = loggerFactory.CreateLogger<RequestIPMiddleware>();
        }

        public async Task Invoke(HttpContext context) {
            _logger.LogInformation("User IP:" + context.Connection.RemoteIpAddress.ToString());
            await _next.Invoke(context);
        }
    }
}

③ 再新建—個 RequestIPExtensions.cs,如下擴展方法經過 IApplicationBuilder 公開中間件:

using Microsoft.AspNetCore.Builder;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace middlewareDemo
{
    public static class RequestIPExtensions {
        public static IApplicationBuilder UseRequestIP(this IApplicationBuilder builder) {
            return builder.UseMiddleware<RequestIPMiddleware>();
        }
    }
}

這樣就編寫好了一箇中間件。
④ 使用中間件。在 Startup.cs 中添加 app.UseRequestIP() 來使用中間件:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {
    loggerFactory.AddConsole();
    app.UseRequestIP();
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
}

而後運行程序,這裏選擇使用 Kestrel
訪問:http://localhost:5000/

 


 

 

成功運行,到這裏咱們還能夠對這個中間件進行進一步改進,增長更多的功能,如限制訪問等。


參考原文

Microsoft 文檔 ASP.NET Core 中間件
ASP.NET Core 中間件(Middleware)詳解
《ASP.NET Core 跨平臺開發從入門到實戰》

返回頂部

相關文章
相關標籤/搜索