本篇將介紹 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 指回起始位置。
 
執行流程如下:

參考