本篇將介紹 ASP.NET Core 3 透過 Middleware 讀寫 Request/Response Body 的用法。
若對 Middleware 基本知識不熟習的話,可以參考 ASP.NET Core 3 系列 - Middleware。
讀取 Request Body
在 Middleware 的 Invoke 可以獲取到 HttpContext,其中會包含 Request 的內容,包含 URL、Header 等。
Request Body 是 Stream 型別,要取出內容,可以透過 StreamReader 如下:
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 30 31 32
| using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http;
namespace MyWebsite { public class FirstMiddleware { private readonly RequestDelegate _next;
public FirstMiddleware(RequestDelegate next) { _next = next; }
public async Task Invoke(HttpContext context) { string requestContent;
using (var reader = new StreamReader(context.Request.Body)) { requestContent = await reader.ReadToEndAsync(); context.Request.Body.Seek(0, SeekOrigin.Begin); }
await _next(context);
Console.WriteLine($"Request.Body={requestContent}"); } } }
|
注意!Seek(0, SeekOrigin.Begin)
非常重要,如果把 Stream 讀完後,不把 Stream Position 還原,之後的 Pipeline、Action 在取得 Request Body 時,會從 Stream 的結尾開始取資料,意味著取出來都是空資料。
讀取 Response Body
Middleware 取得 Response Body 相較於 Request 麻煩很多,因為 Response.Body 的 Stream 並不允許被讀取讀取,但可以被替換。
所以在 Response.Body 開始被寫入之前,先抽換成 MemoryStream;這樣之後的 Pipeline 在寫入 Response.Body 時,實際上都是寫入到被抽換的 MemoryStream 之中。
等到下層 Pipeline 都做完的時候,就可以讀取 MemoryStream 的資料,讀完後再把 MemoryStream 寫到真實的 Response.Body。
範例如下:
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 30 31 32 33 34 35 36 37 38 39 40
| using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http;
namespace MyWebsite { public class SecondMiddleware { private readonly RequestDelegate _next;
public SecondMiddleware(RequestDelegate next) { _next = next; }
public async Task Invoke(HttpContext context) { string responseContent;
var originalBodyStream = context.Response.Body; using (var fakeResponseBody = new MemoryStream()) { context.Response.Body = fakeResponseBody;
await _next(context);
fakeResponseBody.Seek(0, SeekOrigin.Begin); using (var reader = new StreamReader(fakeResponseBody)) { responseContent = await reader.ReadToEndAsync(); fakeResponseBody.Seek(0, SeekOrigin.Begin);
await fakeResponseBody.CopyToAsync(originalBodyStream); } } Console.WriteLine($"Response.Body={responseContent}"); } } }
|
上例 Seek(0, SeekOrigin.Begin)
被呼叫了兩次,原因:
_next(context)
會寫入內容到 fakeResponseBody,導致 Stream Position 會被指到結尾,為了讀取 fakeResponseBody 內容,所以要把 Stream Position 指回起始位置。- 讀取完 fakeResponseBody 內容後,Stream Position 又會被指到結尾,為了把 fakeResponseBody 複製回原本的 Response.Body,所以要把 Stream Position 指回起始位置。
執行流程如下:
參考