Middleware (中介軟體)是指從發出 Request 到接收 Reponse 途中,用來處理特定用途的程式。
微軟官方使用 Pipeline 來形容這個往返的過程,如同水管可以串聯在一起,所有的 Request 和 Reponse 都會一層層的經過這些水管,也可說像是生產線上的流水線。

從上圖可以看出 Request 到最裡面的 Middleware 3 後會在沿著原路回去,經過的順序是先進後出(FILO)。
一個 Middleware 可以分成三部分
- before logic : 指定 Request Pipeline 經過時執行的邏輯
 
- next : 呼叫下一個 Middleware,也可以不呼叫
 
- after logic : 指定 Reponse Pipeline 經過時執行的邏輯
 
設定方式
預設在 Startup 的 Configure 設定,ASP.NET Core 內建了許多好用的 Middleware,如驗證、回應壓縮等等。
自訂 Middleware
使用 Run、Use、Map 自訂 Middleware
- Run : 為所有 Middleware 的最末端,可以說是最後一個行為,當碰到他之後便會開始回流(下方範例的 Middleware - last)
 
- Use : 是用 Use 在 Startup 的 Configure 對 IApplicationBuilder 註冊,可以加入條件判斷再透過呼叫 next 指定執行下一層 Middleware,也可以指定回流時所要執行的動作。例如 : app.UseMVC()
 
- Map : 判斷路由規則是否符合預期,可以依照不同的 URL 指向不同的 Run 及註冊不同的 Use
 
App.Use
範例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | app.Use(async (context, next) => {     await context.Response.WriteAsync("Middleware 1 - request\n");     await next.Invoke();     await context.Response.WriteAsync("Middleware 1 - reponse\n"); });
  app.Use(async (context, next) => {     await context.Response.WriteAsync("Middleware 2 - request\n");     await next.Invoke();     await context.Response.WriteAsync("Middleware 2 - reponse\n"); });
  app.Run(async (context) => {     await context.Response.WriteAsync("Middleware - last\n"); });
   | 
 
瀏覽器上顯示的輸出
1 2 3 4 5
   | Middleware 1 - request Middleware 2 - request Middleware - last  Middleware 2 - reponse Middleware 1 - reponse
   | 
 
從上面的輸出結果可以看出註冊的順序非常重要,Pipeline 會遵循先進後出的原則執行。
Middleware 也可以作為攔截器,不符合條件則不繼續往下送
範例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | app.Use(async (context, next) => {     await context.Response.WriteAsync("Middleware 1 - request\n");     await next.Invoke();     await context.Response.WriteAsync("Middleware 1 - reponse\n"); });
  app.Use(async (context, next) => {     await context.Response.WriteAsync("Middleware 2 - request\n");
           var result = false;     if (result)     {         await next.Invoke();     }     await context.Response.WriteAsync("Middleware 2 - reponse\n"); });
  app.Run(async (context) => {     await context.Response.WriteAsync("Middleware - last\n"); });
   | 
 
輸出結果
1 2 3 4
   | Middleware 1 - request Middleware 2 - request Middleware 2 - reponse Middleware 1 - reponse
   | 
 
App.Map
範例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | app.Use(async (context, next) => {     await context.Response.WriteAsync("Middleware 1 - request\n");     await next.Invoke();     await context.Response.WriteAsync("Middleware 1 - reponse\n"); });
  app.Map("/second", map => {     map.Use(async (context, next) =>     {         await context.Response.WriteAsync("Middleware 2 - request\n");
                   var result = false;         await next.Invoke();         await context.Response.WriteAsync("Middleware 2 - reponse\n");     });
      map.Run(async context =>     {         await context.Response.WriteAsync("Map Second\n");     }); });
  app.Run(async (context) => {     await context.Response.WriteAsync("Middleware - last\n"); });
   | 
 
開啟瀏覽器輸出結果
1 2 3
   | Middleware 1 - request Middleware - last Middleware 1 - reponse
   | 
 
指定 /second (localhost:5001/second) 的輸出結果,可以看出只有對應的 URL 才會執行這層 Middleware
1 2 3 4 5
   | Middleware 1 - request Middleware 2 - request Map Second Middleware 2 - reponse Middleware 1 - reponse
   | 
 
將 Middleware 包成類別
Middleware 必須具備
- 具有 RequestDelegate 類型參數的公用建構函式
 
- 函式名稱為 
Invoke 或 InvokeAsync 的公用方法,此方法必須:
- 傳回 Task
 
- 傳入 HttpContext 類型的參數
 
 
建構函式和 Invoke/InvokeAsync 的其他參數會由DI所填入
範例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | 
 
  public class FirstMiddleware {     private readonly RequestDelegate _next;
                          public FirstMiddleware(RequestDelegate next)     {         this._next = next;     }
                          public async Task InvokeAsync(HttpContext context)     {         await context.Response.WriteAsync("Middleware 1 - request\n");
          await this._next(context);
          await context.Response.WriteAsync("Middleware 1 - reponse\n");     } }
 
  | 
 
在 Startup 的 Configure 加入全域註冊
1
   | app.UseMiddleware<FirstMiddleware>();
   | 
 
也可以只套用在特定的 Controller 或 Action
1 2 3 4 5 6 7 8 9 10
   | [MiddlewareFilter(typeof(FirstMiddleware))] public class HomeController : Controller {     [MiddlewareFilter(typeof(FirstMiddleware))]     public IActionResult Index()     {              }
  }
   | 
 
擴充 Middleware
大部分擴充的 Middleware 都會用一個靜態方法包裝,例如 : UseMVC(),自訂的 Middleware 同樣可以進行包裝。
針對 IApplicationBuilder 進行擴充,並填入自訂的 Middleware
1 2 3 4 5 6 7
   | public static class CustomMiddlewareExtensions {     public static IApplicationBuilder UseFirstMiddleware(this IApplicationBuilder builder)     {         return builder.UseMiddleware<FirstMiddleware>();     } }
   | 
 
擴充包裝完成後就可以使用擴充方法直接註冊
1 2 3 4 5 6 7 8 9
   | public class Startup {          public void Configure(IApplicationBuilder app)     {         app.UseFirstMiddleware();              } }
   | 
 
參考
[1]ASP.NET Core Middleware
[2]ASP.NET Core 2 系列 - Middleware
[3]撰寫自訂的 ASP.NET Core 中介軟體