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 中介軟體