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

從上圖可以看出 Request 到最裡面的 Middleware 3 後會在沿著原路回去,經過的順序是先進後出(FILO)。
一個 Middleware 可以分成三部分
1. before logic : 指定 Request Pipeline 經過時執行的邏輯
2. next : 呼叫下一個 Middleware,也可以不呼叫
3. after logic : 指定 Reponse Pipeline 經過時執行的邏輯
## 設定方式
預設在 Startup 的 Configure 設定,ASP.NET Core 內建了許多好用的 Middleware,如驗證、回應壓縮等等。
## 自訂 Middleware
使用 Run、Use、Map 自訂 Middleware
1. Run : 為所有 Middleware 的最末端,可以說是最後一個行為,當碰到他之後便會開始回流(下方範例的 Middleware - last)
2. Use : 是用 Use 在 Startup 的 Configure 對 IApplicationBuilder 註冊,可以加入條件判斷再透過呼叫 next 指定執行下一層 Middleware,也可以指定回流時所要執行的動作。例如 : app.UseMVC()
3. Map : 判斷路由規則是否符合預期,可以依照不同的 URL 指向不同的 Run 及註冊不同的 Use
### App.Use
範例 :
```csharp=
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");
});
```
瀏覽器上顯示的輸出
```
Middleware 1 - request
Middleware 2 - request
Middleware - last
Middleware 2 - reponse
Middleware 1 - reponse
```
從上面的輸出結果可以看出註冊的順序非常重要,Pipeline 會遵循先進後出的原則執行。
#### Middleware 也可以作為攔截器,不符合條件則不繼續往下送
範例 :
```csharp=
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");
});
```
輸出結果
```
Middleware 1 - request
Middleware 2 - request
Middleware 2 - reponse
Middleware 1 - reponse
```
### App.Map
範例 :
```csharp=
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");
});
```
開啟瀏覽器輸出結果
```
Middleware 1 - request
Middleware - last
Middleware 1 - reponse
```
指定 /second (localhost:5001/second) 的輸出結果,可以看出只有對應的 URL 才會執行這層 Middleware
```
Middleware 1 - request
Middleware 2 - request
Map Second
Middleware 2 - reponse
Middleware 1 - reponse
```
### 將 Middleware 包成類別
Middleware 必須具備
* 具有 RequestDelegate 類型參數的公用建構函式
* 函式名稱為 `Invoke` 或 `InvokeAsync` 的公用方法,此方法必須:
* 傳回 Task
* 傳入 HttpContext 類型的參數
建構函式和 `Invoke`/`InvokeAsync` 的其他參數會由DI所填入
範例 :
```csharp=
/// <summary>
/// class FirstMiddleware
/// </summary>
public class FirstMiddleware
{
private readonly RequestDelegate _next;
/// <summary>
/// Initializes a new instance of the <see cref="FirstMiddleware"/> class.
/// </summary>
/// <param name="next">The next.</param>
public FirstMiddleware(RequestDelegate next)
{
this._next = next;
}
/// <summary>
/// Invokes the asynchronous.
/// </summary>
/// <param name="context">The context.</param>
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 加入全域註冊
```csharp=
app.UseMiddleware<FirstMiddleware>();
```
也可以只套用在特定的 Controller 或 Action
```csharp=
[MiddlewareFilter(typeof(FirstMiddleware))]
public class HomeController : Controller
{
[MiddlewareFilter(typeof(FirstMiddleware))]
public IActionResult Index()
{
// ...
}
}
```
### 擴充 Middleware
大部分擴充的 Middleware 都會用一個靜態方法包裝,例如 : `UseMVC()`,自訂的 Middleware 同樣可以進行包裝。
針對 IApplicationBuilder 進行擴充,並填入自訂的 Middleware
```csharp=
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseFirstMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<FirstMiddleware>();
}
}
```
擴充包裝完成後就可以使用擴充方法直接註冊
```csharp=
public class Startup
{
// ...
public void Configure(IApplicationBuilder app)
{
app.UseFirstMiddleware();
// ...
}
}
```
## 參考
[1][ASP.NET Core Middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1)
[2][ASP.NET Core 2 系列 - Middleware](https://blog.johnwu.cc/article/ironman-day03-asp-net-core-middleware.html)
[3][撰寫自訂的 ASP.NET Core 中介軟體](https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/middleware/write?view=aspnetcore-3.1)
###### tags: `C#` `Dotnet Core` `Middleware`