0%

ASP.NET Core - Middleware

Middleware (中介軟體)是指從發出 Request 到接收 Reponse 途中,用來處理特定用途的程式。

微軟官方使用 Pipeline 來形容這個往返的過程,如同水管可以串聯在一起,所有的 Request 和 Reponse 都會一層層的經過這些水管,也可說像是生產線上的流水線。

Middleware

從上圖可以看出 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

範例 :

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 類型參數的公用建構函式
  • 函式名稱為 InvokeInvokeAsync 的公用方法,此方法必須:
    • 傳回 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
/// <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 加入全域註冊

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