6 ASP.NET Core中間件
ASP.NET Core 中間件是位于請求處理管道的一系列元件,用來處理請求和響應,在請求管道線上可以有一個或者多個中間件
當一個新的HTTP請求到達,第一個中間件将執行,這個中間件将執行下面兩個過程中的一個
1 生成響應并且發送響應到用戶端
2 将請求傳遞到下一個中間件
注意中間件執行的順序按照他們在應用程式中注冊的順序執行,響應的順序則按照相反的方向執行,假設我們有4個中間件-M1,M2,M3和M4,他們在應用程式中定義的順序M1>M2>M3>M4,針對一個請求
1 中間件處理的順序是M1>M2>M3>M4.
2 中間件響應傳回到用戶端的順序為M4>M3>M2>M1
ASP.NET Core為什麼要使用中間件?中間件處理應用程式發出的請求,可以增強和建構應用程式的進階功能,如身份驗證、授權、日志記錄等,ASP.NET Core 中有大量的内置中間件,我們可以在我們應用程式中使用這些中間件,删除不需要的中間件,進而優化我們應用程式的速度
6.1 ASP.NET Core客戶自定義中間件
在ASP.NET Core中我們可以自定義中間件,客戶自定義的中間件有下列四種形式:
1 内容生成中間件(Content-Generating middleware)
2 短路中間件(Short-Circuiting middleware)
3 請求編輯中間件(Request-Editing middleware)
4 響應編輯中間件(Response-Editing middleware)
每個中間件必須具備兩個功能:
1 Microsoft.AspNetCore.Http命名空間下的RequestDeleg-ate,這個對象處理http請求,RequestDelegate代表請求管道中的下一個中間件元件,并通過依賴注入到中間件的構造中2 當中間件被調用時調用Invoke方法,在這個方法中我們可以寫自己代碼我們針對這四種類型的中間件單獨建構一個例子來說明
6.1.1 内容生成中間件(Content-Generating middleware)
内容生成中間件生成内容或響應,我們建立這個中間件傳回響應到用戶端,我們通過一個簡單例子來了解如何工作
在根目錄一下建立一個Moddlewares檔案夾,在這個檔案夾下添加一個ContextMiddleware.cs檔案,代碼如下:
namespace AspNetCore.Configuration.Middlewares { public class ContentMiddleware { private RequestDelegate _nextDelegate; public ContentMiddleware(RequestDelegate next) { _nextDelegate = next; } public async Task Invoke(HttpContext httpContext) { if (httpContext.Request.Path == "/middleware") { await httpContext.Response.WriteAsync("這是Context 中間件"); } else { _nextDelegate(httpContext); } } } }
注意中間件類沒有實作任何接口或者繼承任何公共基類,它有一個構造函數使用了RequestDelegate類型,構造函數的參數由MVC自動提供,RequestDelegate對象表示下一個執行的中間件
中間件定義了Invoke方法,當.NET 接收到http請求時這個方法被調用,Invoke方法有一個HttpContext的參數類型,該參數包含了Http 請求和傳回用戶端的響應在ContentMiddleware.cs類中,Invoke方法檢查HTTP請求并且檢查請求的URL中是否包含/middleware , 如果有,它将發送簡單的文本響應到用戶端,如果沒有,它将轉發請求到下一個中間件現在在Program.cs類中使用UseMiddleware()方法注冊中間件,代碼如下:
using AspNetCore.Configuration.Middlewares; using AspNetCore.Configuration.Services; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); builder.Services.AddSingleton<TotalUsers>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseMiddleware<ContentMiddleware>(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
運作應用程式然後進入https://localhost:7034/middleware,你會看到中間件輸出的内容,如下圖所示:
如果請求的URL不是 https://localhost:7034/middleware,ContextMiddleware傳遞請求到下一個中間件,注意請求管道中已經沒有中間件,是以請求直接傳回
中間件中使用依賴注入
我們可以使用DI将服務注入到中間件,在前面的我們建立一個TotalUsers,現在我們使用中間件的構造函數注入該服務并在中間件中使用這個服務,是以,我們更新一下
using AspNetCore.Configuration.Services; namespace AspNetCore.Configuration.Middlewares { public class ContentMiddleware { private RequestDelegate _nextDelegate; private TotalUsers _totalUsers; public ContentMiddleware(RequestDelegate next, TotalUsers totalUsers) { _nextDelegate = next; _totalUsers = totalUsers; } public async Task Invoke(HttpContext httpContext) { if (httpContext.Request.Path == "/middleware") { await httpContext.Response.WriteAsync("this message come from ContextMiddleware" +" TotalUsers=" + _totalUsers.TUsers()); } else { _nextDelegate(httpContext); } } } }
運作應用程式,進入https://localhost:7034/middleware,現在你将看到總使用者人數:
6.1.2 短路中間件(Short-Circuiting middleware)
短路中間件會阻止後面中間件的執行,因為它會使整個請求管道短路,讓我們建立一個短路中間件
在Middlewares檔案夾下建立一個ShortCircuitMiddleware.cs類,在這個類中添加如下代碼:
namespace AspNetCore.Configuration.Middlewares { public class ShortCircuitMiddleware { private RequestDelegate _next; public ShortCircuitMiddleware(RequestDelegate requestDelegate) { _next = requestDelegate; } public async Task Invoke(HttpContext context) { if (context.Request.Headers["User-Agent"].Any(v => v.Contains("Firefox"))) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; } else { await _next(context); } } } }
如果用戶端浏覽器使用的是firefox, ASP.NET Core中間件傳回401 Unauthorized
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
現在進入Program.cs類注冊這個短路中間件,確定在Context-Middleware.cs中間件之前注冊該中間件
app.UseMiddleware<ShortCircuitMiddleware>(); app.UseMiddleware<ContentMiddleware>();
記住中間件執行的順序是按照在Program類中注冊的順序執行的,我們想讓ShortCircuitMiddleware中間件在ContentMiddleware中間件之前執行,是以我們把它放在第一個位置
短路中間件檢查User-Agent在請求頭查找是否是firefox浏覽器,如果是它不會将請求轉發到下一個中間件,而是直接傳回未授權狀态碼,針對除firefox以外的浏覽器會将請求轉發到ContextMiddleware.cs
運作程式,在firefox輸入https://localhost:7034/middleware
在别的浏覽器中輸入https://localhost:7034/middleware
6.1.3 請求編輯中間件(Request-Editing middleware)
請求編輯中間件是針對http請求進行編輯,則不會生成響應,我們在Middlewares檔案夾内建立一個RequestEditingMiddleware中間件,添加下面代碼:
namespace AspNetCore.Configuration.Middlewares { public class RequestEditingMiddleware { private RequestDelegate _next; public RequestEditingMiddleware(RequestDelegate next) => _next = next; public async Task Invoke(HttpContext httpContext) { httpContext.Items["Firefox"] = httpContext.Request.Headers["User-Agent"].Any(v => v.Contains("Firefox")); await _next(httpContext); } } }
在上面代碼中我們看到對HTTP請求進行修改而沒有發送任何響應,首先檢查請求是否來自于firefox浏覽器,如果是我們往Http-Context字典中添加一個key為Firefox值為true的鍵值對,如下代碼:
httpContext.Items["Firefox"] = httpContext.Request.Headers["User-Agent"].Any(v => v.Contains("Firefox"));
現在我們修改一下ShortCircuitMiddleware.cs中間件,删除下面代碼:
if (context.Request.Headers["User-Agent"].Any(v => v.Contains("Firefox")))
用下面代碼替換
if(context.Items["Firefox"] as bool? == true)
最新的ShortCircuitMiddleware代碼如下:
namespace AspNetCore.Configuration.Middlewares { public class ShortCircuitMiddleware { private RequestDelegate _next; public ShortCircuitMiddleware(RequestDelegate requestDelegate) { _next = requestDelegate; } public async Task Invoke(HttpContext context) { //if (context.Request.Headers["User-Agent"].Any(v => v.Contains("Firefox"))) if(context.Items["Firefox"] as bool? == true) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; } else { await _next(context); } } } }
我們把RequestEditingMiddleware.cs注冊到ShortCircuitMid-dleware中間件前面
是以在Program.cs中注冊中間件的順序如下:
app.UseMiddleware<RequestEditingMiddleware>(); app.UseMiddleware<ShortCircuitMiddleware>(); app.UseMiddleware<ContentMiddleware>();
運作程式,firefox輸入https://localhost:7034/middleware ,由于傳回未授權的http是以你擷取一個空白頁
6.1.4 響應編輯中間件(Response-Editing middleware)
響應編輯中間件對響應進行編輯,在上面我們擷取一個空白頁,空白頁針對應用程式是不友好的,我們将顯示友好的消息,在這裡将使用響應編輯中間件,我們提供文本的輸出讓使用者在浏覽器中看到在Middlewares檔案夾下添加一個ResponseEditingMiddle-ware.cs類,代碼如下:
namespace AspNetCore.Configuration.Middlewares { public class ResponseEditingMiddleware { private RequestDelegate _next; public ResponseEditingMiddleware(RequestDelegate next) { _next=next; } public async Task Invoke(HttpContext httpContext) { await _next(httpContext); if (httpContext.Response.StatusCode==401) { await httpContext.Response.WriteAsync("Firefox browser not authorized"); } else if(httpContext.Response.StatusCode==404) { await httpContext.Response.WriteAsync("No Response Generated"); } } } }
在上面的代碼如果HTTP響應的狀态碼是401,我們添加文本Firefox browser not authorized到響應中,回想一下我們在Short-Circuiting中間中生成401未授權的響應
響應編輯中間件僅僅編輯其它中間件的響應,是以它必須放到請求管道其它中間件的前面,是以注冊的位置很重要
app.UseMiddleware<ResponseEditingMiddleware>(); app.UseMiddleware<RequestEditingMiddleware>(); app.UseMiddleware<ShortCircuitMiddleware>(); app.UseMiddleware<ContentMiddleware>();
現在運作你的應用程式并且在Firefox浏覽器中輸入URL-https://localhost:7034/middleware,你将擷取到響應文本-Firefox browser not unthorized , 圖檔顯示如下
接收404狀态碼
在響應編輯中間件中,有else if 代碼塊:
else if(httpContext.Response.StatusCode==404) { await httpContext.Response.WriteAsync("No Response Generated"); }
當狀态碼是404時執行該代碼,當請求的資源在應用程式中沒有發現時該狀态碼會自動生成
在浏覽器輸入一個url-https://localhost:7034/tutorials,你會在浏覽器中看到No Response Generated 消息
7 路由中間件路由中間件的主要任務是把請求指派給各自的資源,我們在program.cs類中建立路由來執行這些操作我們通過使用app.UseRouting()讓應用程式來使用路由,我們在應用程式program類底部建立一個預設的路由
app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
總結
這節我們主要介紹了中間件的概念以及内容生成中間件(Content-Generating middleware),短路中間件(Short-Circuiting middleware),請求編輯中間件(Request-Editing middleware),響應編輯中間件(Response-Editing middleware)等源代碼位址https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/Fundamentals/AspNetCore.Configuration/AspNetCore.Configuration參考文獻[1]https://www.yogihosting.com/aspnet-core-configurations/#content-generating-middleware
[2]https://www.programmingwithwolfgang.com/middleware-in-asp-net-core-mvc/