中間件管道模型 中間件的配置 自定義中間件 中間件是一類裝配在應用管道的代碼,負責處理請求和響應。每個中間件都可在管道中的下一個組件前後執行工作,並選擇是否將請求傳遞到管道中的下一個中間件。在Startup.Configure方法中可以進行中間件的裝配。 中間件管道模型 中間件管道模型如下圖所示: ...
- 中間件管道模型
- 中間件的配置
- 自定義中間件
中間件是一類裝配在應用管道的代碼,負責處理請求和響應。每個中間件都可在管道中的下一個組件前後執行工作,並選擇是否將請求傳遞到管道中的下一個中間件。在Startup.Configure方法中可以進行中間件的裝配。
中間件管道模型
中間件管道模型如下圖所示:
ASP.NET Core請求管道包含一系列請求委托,沿黑色箭頭依次被調用執行,每個委托均可在下一個委托前後執行操作。這種模型也被形象地稱為“俄羅斯套娃”。
一個中間件可以是匿名方法的顯示嵌入到管道中,也可以封裝為單獨的類便於重用,嵌入式的中間件就像這樣:
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
中間件的配置
配置中間件會使用到三個擴展方法:
- Use
- Run
- Map
Use
Use用來將多個中間件按添加順序鏈接到一起:
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("middleware1 begin\r\n");
await next.Invoke();
await context.Response.WriteAsync("middleware1 end\r\n");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("middleware2 begin\r\n");
await next.Invoke();
await context.Response.WriteAsync("middleware2 end\r\n");
});
app.Run(async context =>
{
await context.Response.WriteAsync("end of pipeline.\r\n");
});
這三個中間件配置到管道後,輸出的結果與管道模型的圖示是一致的:
middleware1 begin
middleware2 begin
end of pipeline.
middleware2 end
middleware1 end
可以看到除了最後一個中間件,前面的中間件都調用了await next.Invoke(),next參數表示管道中的下一個委托,如果不調用 next,後面的中間件就不知執行,這稱為管道的短路。
通常中間件都應該自覺調用下一個中間件,但有的中間件會故意造成短路,比如授權中間件、靜態文件中間件等。
Run
Run的委托中沒有next參數,這就意味著它會稱為最後一個中間件(終端中間件),此外可以故意短路的Use委托也可能會成為終端中間件。
Map,提供了創建管道分支的能力
Map擴展用作約定來創建管道分支,會基於給定請求路徑的匹配項來創建請求管道分支,如果請求路徑以給定路徑開頭,則執行分支。
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}
private static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}
public void Configure(IApplicationBuilder app)
{
app.Map("/map1", HandleMapTest1);
app.Map("/map2", HandleMapTest2);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
這段代碼為管道創建了三個分支:
URL | Response |
---|---|
localhost:1234 | Hello from non-Map delegate. |
localhost:1234/map1 | Map Test 1 |
localhost:1234/map2 | Map Test 2 |
Map還支持嵌套
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a" processing
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b" processing
});
});
MapWhen,滿足條件時創建分支
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
branch => {
});
此外,UseWhen也可以根據條件創建分支,它與MapWhen的區別在於,使用UseWhen創建的分支如果不包含終端中間件,則會重新匯入主管道。
自定義中間件
通常使用內置的中間件可滿足大多數場合,但如果有需要也可以創建自定義中間件。
假設有這樣一個嵌入式中間件,可以通過查詢字元串設置當前請求的區域,比如https://localhost:5000/?culture=zh-cn,會將CurrentCulture設置為Chinese Simplified。現在要將其封裝為可重用的獨立的中間件。
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline
await next();
});
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
在開始封裝前,先瞭解一下對中間件的要求:
- 中間件必須具有類型為RequestDelegate的參數的公共構造函數,之前代碼中看到next.Invoke(),其中next就是下一個中間件的RequestDelegate;
- 名為 Invoke 或 InvokeAsync 的公共方法。而且這個方法的返回類型為Task,且第一個參數的必須是HttpContext,其它的參數由DI容器解析。
基於上述要求,編寫的中間件為:
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline
//await _next.Invoke(context);
await _next(context);
}
}
然後就可以使用了:
app.UseMiddleware<RequestCultureMiddleware>();
還可以進一步封裝為IApplicationBuilder的擴展方法:
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
然後就可以像內置的中間件一樣了:
app.UseRequestCulture();