中間件是一類裝配在應用管道的代碼,負責處理請求和響應。每一箇中間件均可在管道中的下一個組件先後執行工做,並選擇是否將請求傳遞到管道中的下一個中間件。在Startup.Configure方法中能夠進行中間件的裝配。app
中間件管道模型以下圖所示:
async
ASP.NET Core請求管道包含一系列請求委託,沿黑色箭頭依次被調用執行,每一個委託都可在下一個委託先後執行操做。這種模型也被形象地稱爲「俄羅斯套娃」。函數
一箇中間件能夠是匿名方法的顯示嵌入到管道中,也能夠封裝爲單獨的類便於重用,嵌入式的中間件就像這樣:ui
public void Configure(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Hello, World!"); }); }
配置中間件會使用到三個擴展方法:this
Use用來將多箇中間件按添加順序連接到一塊兒:code
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,後面的中間件就不知執行,這稱爲管道的短路。
一般中間件都應該自覺調用下一個中間件,但有的中間件會故意形成短路,好比受權中間件、靜態文件中間件等。blog
Run的委託中沒有next參數,這就意味着它會稱爲最後一箇中間件(終端中間件),此外能夠故意短路的Use委託也可能會成爲終端中間件。ip
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 |
app.Map("/level1", level1App => { level1App.Map("/level2a", level2AApp => { // "/level1/level2a" processing }); level1App.Map("/level2b", level2BApp => { // "/level1/level2b" processing }); });
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}"); }); }
在開始封裝前,先了解一下對中間件的要求:
基於上述要求,編寫的中間件爲:
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();