前幾天有群友在群裡問如何在我之前的文章《ASP.NET Core WebApi返回結果統一包裝實踐》的時候有點疑問,主要的疑問點就是關於Respouse的讀取的問題。在之前的文章《深入探究ASP.NET Core讀取Request.Body的正確方式》曾分析過關於Request的讀取問題,需要讀取Response的場景同樣經常遇到,比如讀取輸出資訊或者包裝一下輸出結果等。無獨有偶Response的讀取同樣存在類似的問題,本文我們便來分析一下如何進行Response的Body讀取。
我們在日常的使用中是如何讀取流呢?很簡單,直接使用StreamReader
去讀取,方式如下
public override void OnResultExecuted(ResultExecutedContext context)
{
//操作流之前恢復一下操作位
context.HttpContext.Response.Body.Position = 0;
StreamReader stream = new StreamReader(context.HttpContext.Response.Body);
string body = stream.ReadToEnd();
_logger.LogInformation("body content:" + body);
context.HttpContext.Response.Body.Position = 0;
base.OnResultExecuted(context);
}
程式碼很簡單,直接讀取即可,可是這樣讀取是有問題的會丟擲異常System.ArgumentException:「Stream was not readable.」
異常資訊就是的意思是當前Stream不可讀,也就是Respouse的Body是不可以被讀取的。關於StreamReader到底和Stream有啥關聯,我們在之前的文章深入探究ASP.NET Core讀取Request.Body的正確方式一文中有過原始碼分析,這裡就不在贅述了,有興趣的同學可以自行翻閱,強烈建議在閱讀本文之前可以看一下那篇文章,方便更容易瞭解。
如何解決上面的問題呢?方式也很簡單,比如你想在你的程式中保證Response的Body都是可讀的,你可以定義一箇中介軟體解決這個問題。
public static IApplicationBuilder UseResponseBodyRead(this IApplicationBuilder app)
{
return app.Use(async (context, next) =>
{
//獲取原始的Response Body
var originalResponseBody = context.Response.Body;
try
{
//宣告一個MemoryStream替換Response Body
using var swapStream = new MemoryStream();
context.Response.Body = swapStream;
await next(context);
//重置標識位
context.Response.Body.Seek(0, SeekOrigin.Begin);
//把替換後的Response Body複製到原始的Response Body
await swapStream.CopyToAsync(originalResponseBody);
}
finally
{
//無論異常與否都要把原始的Body給切換回來
context.Response.Body = originalResponseBody;
}
});
}
本質就是先用一個可操作的Stream比如咱們這裡的MemoryStream
替換預設的ResponseBody,讓後續對ResponseBody的操作都是針對新的ResponseBody進行操作,完成之後把替換後的ResponseBody複製到原始的ResponseBody。最終無論異常與否都要把原始的Body給切換回來。需要注意的是,這個中介軟體的位置儘量要放在比較靠前的位置註冊,至少也要保證在你所有要操作ResponseBody之前的位置註冊。如下所示
var app = builder.Build();
app.UseResponseBodyRead();
通過上面我們瞭解到了ResponseBody是不可以被讀取的,至於為什麼呢,這個我們需要通過相關原始碼瞭解一下。通過HttpContext
類的原始碼我們可以看到相關定義
public abstract class HttpContext
{
public abstract HttpResponse Response { get; }
}
這裡看到HttpContext
本身是個抽象類,看一下它的屬性HttpResponse
類的定義也是一個抽象類
public abstract class HttpResponse
{
}
由上面可知Response
屬性是抽象的,所以抽象類HttpResponse
必然包含一個子類去實現它,否則沒辦法直接操作相關方法。這裡我們介紹一個網站https://source.dot.net用它可以更輕鬆的閱讀微軟類庫的原始碼,比如CLR、ASP.NET Core、EF Core等等,雙擊一個類或者屬性方法可以查詢參照和定義它們的地方,非常方便,它的原始碼都是最新版本的,來源就是GitHub上的相關倉庫。找到範例化HttpResponse
的為位置在HttpContext
的子類DefaultHttpContext
類中[點選檢視原始碼