欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

ASP.NET Core自定義中間件如何讀取Request.Body與Response.Body的內(nèi)容詳解

 更新時(shí)間:2020年05月25日 09:00:37   作者:Lamond Lu  
這篇文章主要給大家介紹了關(guān)于在ASP.NET Core自定義中間件中如何讀取Request.Body與Response.Body的內(nèi)容,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用ASP.NET Core具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

背景#

最近在徒手造輪子,編寫(xiě)一個(gè)ASP.NET Core的日志監(jiān)控器,其中用到了自定義中間件讀取Request.Body和Response.Body的內(nèi)容,但是編寫(xiě)過(guò)程,并不像想象中的一帆風(fēng)順,ASP.NET Core針對(duì)Request.Body和Response.Body的幾個(gè)特殊設(shè)計(jì),導(dǎo)致了完成以上功能需要繞一些彎路。

 

原始代碼#

為了讀取Request.Body和Response.Body的內(nèi)容,我的實(shí)現(xiàn)思路如下:

創(chuàng)建一個(gè)LoggerMiddleware的中間件,將它放置在項(xiàng)目中間件管道的頭部。因?yàn)楦鶕?jù)ASP.NET Core的中間件管道設(shè)計(jì),只有第一個(gè)中間件才能獲取到原始的請(qǐng)求信息和最終的響應(yīng)信息。

Request.Body和Response.Body屬性都是Steram類型, 在LoggerMiddleware中間件的InvokeAsync方法中,我們可以分別使用StreamReader讀取Request.Body和Response.Body的內(nèi)容。

根據(jù)以上思路,我編寫(xiě)了以下代碼。

LoggerMiddleware.cs

	public class LoggerMiddleware
 {
 private readonly RequestDelegate _next;

 public LoggerMiddleware(RequestDelegate next)
 {
  _next = next;
 }

 public async Task InvokeAsync(HttpContext context)
 {
  var requestReader = new StreamReader(context.Request.Body);

 		var requestContent = requestReader.ReadToEnd();
 		Console.WriteLine($"Request Body: {requestContent}");

 		await _next(context);

 		var responseReader = new StreamReader(context.Response.Body);
 		var responseContent = responseReader.ReadToEnd();
 		Console.WriteLine($"Response Body: {responseContent}");
 }
 }

Startup.cs

 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
 {
 if (env.IsDevelopment())
 {
  app.UseMiddleware<LoggerMiddleware>();
  app.UseDeveloperExceptionPage();
 }
 else
 {
  app.UseHsts();
 }

 app.UseHttpsRedirection();
 app.UseMvc();
 }

問(wèn)題1:Response.Body的Stream不可讀#

這里為了測(cè)試我創(chuàng)建了一個(gè)默認(rèn)的ASP.NET Core WebApi項(xiàng)目。當(dāng)運(yùn)行程序,使用GET方式調(diào)用/api/values之后,控制臺(tái)會(huì)返回第一個(gè)需要處理的錯(cuò)誤。

System.ArgumentException: Stream was not readable.

即ASP.NET Core默認(rèn)創(chuàng)建的Response.Body屬性是不可讀的。

這一點(diǎn)我們可以通過(guò)打斷點(diǎn)看到Response.Body屬性的CanRead值是false。

這就很糟糕了,ASP.NET Core默認(rèn)并不想讓我們?cè)谥虚g件中直接讀取Response.Body中的信息。

這里看似的無(wú)解,但是我們可以轉(zhuǎn)換一下思路,既然ASP.NET Core默認(rèn)將Response.Body是不可讀的,那么我們就使用一個(gè)可讀可寫(xiě)的Stream對(duì)象將其替換掉。這樣當(dāng)所有中間件都依次執(zhí)行完之后,我們就可以讀取Response.Body的內(nèi)容了。

public async Task InvokeAsync(HttpContext context)
{
	 var requestReader = new StreamReader(context.Request.Body);

 var requestContent = requestReader.ReadToEnd();
 Console.WriteLine($"Request Body: {requestContent}");

 using (var ms = new MemoryStream())
 {
  context.Response.Body = ms;
  await _next(context);

  context.Response.Body.Position = 0;

  var responseReader = new StreamReader(context.Response.Body);

  var responseContent = responseReader.ReadToEnd();
  Console.WriteLine($"Response Body: {responseContent}");

  context.Response.Body.Position = 0;
 }
}

注意:

  • 讀取Response.Body的時(shí)候,需要設(shè)置Position = 0, 這樣是為了重置指針,如果不這樣做的話,會(huì)導(dǎo)致讀取的流不正確。
  • 這里千萬(wàn)不要用using包裹StreamReader, 因?yàn)镾treamReader會(huì)在讀取完Stream內(nèi)容之后,將Stream關(guān)閉,導(dǎo)致后續(xù)由于Stream關(guān)閉,而不能再次讀取Stream中的內(nèi)容。如果必須使用,請(qǐng)使用StreamReader的以下重載,將leaveOpen參數(shù)設(shè)置為true, 確保StreamReader對(duì)象被銷毀的時(shí)候不會(huì)自動(dòng)關(guān)閉讀取的Stream.
public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen);

重新啟動(dòng)程序,請(qǐng)求/api/values, 我們就得到的正確的結(jié)果。

進(jìn)一步完善代碼#

以上代碼實(shí)現(xiàn),看似已經(jīng)能夠讀取Response.Body的內(nèi)容了,但是其實(shí)還是有問(wèn)題的。

回想一下,我們做出以上方案的前提是,當(dāng)前LoggerMiddleware中間件必須位于中間件管道的頭部。

如果不能保證這個(gè)約定, 就會(huì)出現(xiàn)問(wèn)題,因?yàn)槲覀冊(cè)贚oggerMiddleware中間件中將Response.Body屬性指向了一個(gè)新的可讀可寫(xiě)的Stream對(duì)象。如果LoggerMiddleware中間件之前的某個(gè)中間件中設(shè)置過(guò)Response.Body, 就會(huì)導(dǎo)致這部分設(shè)置丟失。

因此正確的設(shè)置方式應(yīng)該是這樣的:

 public async Task InvokeAsync(HttpContext context)
 {
 var originalResponseStream = context.Response.Body;

 var requestReader = new StreamReader(context.Request.Body);
 
 var requestContent = requestReader.ReadToEnd();
 Console.WriteLine($"Request Body: {requestContent}");
 

 using (var ms = new MemoryStream())
 {
  context.Response.Body = ms;
  await _next(context);


  ms.Position = 0;
  var responseReader = new StreamReader(ms);

  var responseContent = responseReader.ReadToEnd();
  Console.WriteLine($"Response Body: {responseContent}");

  ms.Position = 0;

  await ms.CopyToAsync(originalResponseStream);
  context.Response.Body = originalResponseStream;
 }
 }

代碼解釋:

  • 這里當(dāng)進(jìn)入LoggerMiddleware中間件時(shí),我們將之前中間件操作完成之后的Response.Body對(duì)象對(duì)應(yīng)的原始Stream, 保存在一個(gè)臨時(shí)變量中
  • 當(dāng)LoggerMiddelware中間件的任務(wù)完成之后,我們需要將后續(xù)產(chǎn)生的Response.Body流追加到原始Stream中,然后將Response.Body對(duì)象重置為這個(gè)新的Stream。

至此Repsonse.Body的問(wèn)題都解決,下面我們?cè)賮?lái)看一下Request.Body的問(wèn)題。

問(wèn)題2:Request.Body的內(nèi)容可以正確的顯示,但是后續(xù)的ModelBinding都失敗了#

下面我們來(lái)請(qǐng)求POST /api/values, Request.Body里面的內(nèi)容是字符串"123123"

 

服務(wù)器端返回了400錯(cuò)誤, 錯(cuò)誤信息

A non-empty request body is required.

這里就很奇怪,為啥請(qǐng)求體是空呢?我們回到中間件部分代碼,這里我們?cè)谧x取完Request.Body中的Stream之后,沒(méi)有將Stream的指針重置,當(dāng)前指針已經(jīng)是Stream的尾部,所以后續(xù)ModelBinding的時(shí)候,讀取不到Stream的內(nèi)容了。

 public async Task InvokeAsync(HttpContext context)
 {
 ...
 var requestReader = new StreamReader(context.Request.Body);
 
 var requestContent = requestReader.ReadToEnd();
 Console.WriteLine($"Request Body: {requestContent}");
 ...
 }

于是,這里我們需要采取和Response.Body相同的處理方式,在讀取完Request.Body之后,我們需要將Request.Body的Stream指針重置

 public async Task InvokeAsync(HttpContext context)
 {
 ...
 var requestReader = new StreamReader(context.Request.Body);
 
 var requestContent = requestReader.ReadToEnd();
 Console.WriteLine($"Request Body: {requestContent}");
 context.Request.Body.Position = 0;
 ...
 }

你一定覺(jué)著至此問(wèn)題就解決了,不過(guò)ASP.NET Core和你又開(kāi)了一個(gè)玩笑。

當(dāng)你重新請(qǐng)求POST /api/values之后,你會(huì)得到以下結(jié)果。

錯(cuò)誤原因:

System.NotSupportedException: Specified method is not supported.

翻譯過(guò)來(lái)就是指定方法不支持。到底不支持啥呢?在代碼上打上斷點(diǎn),你會(huì)發(fā)現(xiàn)Request.Body的CanSeek屬性是false, 即Request.Body的Stream, 你是不能隨便移動(dòng)指針的,只能按順序讀取一次,默認(rèn)不支持反復(fù)讀取。

那么如何解決這個(gè)問(wèn)題呢?

你可以在使用Request對(duì)象中的EnableRewind或者EnableBuffering。 這2個(gè)方法的作用都是在內(nèi)存中創(chuàng)建緩沖區(qū)存放Request.Body的內(nèi)容,從而允許反復(fù)讀取Request.Body的Stream。

說(shuō)明: 其實(shí)EnableBuffering方法內(nèi)部就只直接調(diào)用的EnableRewind方法。

下面我們修改代碼

 public async Task InvokeAsync(HttpContext context)
 {
 context.Request.EnableBuffering();
 var requestReader = new StreamReader(context.Request.Body);

 var requestContent = requestReader.ReadToEnd();
 Console.WriteLine($"Request Body: {requestContent}");
 context.Request.Body.Position = 0;


 using (var ms = new MemoryStream())
 {
  context.Response.Body = ms;
  await _next(context);


  ms.Position = 0;
  var responseReader = new StreamReader(ms);

  var responseContent = responseReader.ReadToEnd();
  Console.WriteLine($"Response Body: {responseContent}");

  ms.Position = 0;
 }
 }

再次請(qǐng)求POST /api/values, api請(qǐng)求被正確的處理了。

源代碼: https://github.com/lamondlu/webapi-logger

總結(jié)

到此這篇關(guān)于ASP.NET Core自定義中間件中如何讀取Request.Body與Response.Body的內(nèi)容的文章就介紹到這了,更多相關(guān)ASP.NET Core自定義中間件讀取Request.Body與Response.Body內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • asp.net ajax實(shí)現(xiàn)無(wú)刷新驗(yàn)證碼

    asp.net ajax實(shí)現(xiàn)無(wú)刷新驗(yàn)證碼

    實(shí)現(xiàn)ajax無(wú)刷新驗(yàn)證碼首先需要兩個(gè)aspx頁(yè)面,第一個(gè)用來(lái)展示,另一個(gè)用來(lái)后臺(tái)刷新驗(yàn)證碼
    2011-10-10
  • ASP.NET Core中預(yù)壓縮靜態(tài)文件的方法步驟

    ASP.NET Core中預(yù)壓縮靜態(tài)文件的方法步驟

    這篇文章主要給大家介紹了關(guān)于ASP.NET Core中如何預(yù)壓縮靜態(tài)文件的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用ASP.NET Core具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • IIS服務(wù)器發(fā)布ASP.NET項(xiàng)目

    IIS服務(wù)器發(fā)布ASP.NET項(xiàng)目

    如何在云服務(wù)器上部署一個(gè)項(xiàng)目,需要做哪些配置準(zhǔn)備,本文就來(lái)介紹一下IIS服務(wù)器發(fā)布ASP.NET項(xiàng)目,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-03-03
  • 關(guān)于VS2012自帶的 性能分析 工具使用實(shí)例(圖文介紹)

    關(guān)于VS2012自帶的 性能分析 工具使用實(shí)例(圖文介紹)

    本篇文章小編為大家介紹,關(guān)于VS2012自帶的 性能分析 工具使用實(shí)例(圖文介紹),需要的朋友參考下
    2013-04-04
  • C#中的cookie編程簡(jiǎn)單實(shí)例與說(shuō)明

    C#中的cookie編程簡(jiǎn)單實(shí)例與說(shuō)明

    這篇文章介紹了C#中的cookie編程簡(jiǎn)單實(shí)例與說(shuō)明,有需要的朋友可以參考一下
    2013-07-07
  • .Net頁(yè)面局部更新引發(fā)的思考

    .Net頁(yè)面局部更新引發(fā)的思考

    這篇文章主要是由.Net頁(yè)面局部更新引發(fā)的一系列思考,整理了實(shí)現(xiàn)局部更新的解決方案及改進(jìn)方案,感興趣的小伙伴們可以參考一下
    2016-06-06
  • ASP.NET中application對(duì)象的使用介紹

    ASP.NET中application對(duì)象的使用介紹

    這篇文章主要介紹了ASP.NET中application對(duì)象的使用,需要的朋友可以參考下
    2014-03-03
  • asp.net 無(wú)刷新附件上傳實(shí)現(xiàn)方法

    asp.net 無(wú)刷新附件上傳實(shí)現(xiàn)方法

    一直以來(lái)附件上傳都是個(gè)很郁悶的問(wèn)題,剛開(kāi)始是利用js添加input file 然后一起提交來(lái)實(shí)現(xiàn)多文件上傳,在使用163郵箱的時(shí)候很是羨慕它的附件上傳部分(選擇完文件就提交,可以多個(gè)文件一起上傳,而且還可以獲取上傳進(jìn)度),這時(shí)就很想自己也寫(xiě)個(gè)那樣的東西出來(lái)。
    2010-01-01
  • C#后臺(tái)調(diào)用前臺(tái)javascript的五種方法小結(jié)

    C#后臺(tái)調(diào)用前臺(tái)javascript的五種方法小結(jié)

    于項(xiàng)目需要,用到其他項(xiàng)目組用VC開(kāi)發(fā)的組件,在web后臺(tái)代碼無(wú)法訪問(wèn)這個(gè)組件,所以只好通過(guò)后臺(tái)調(diào)用前臺(tái)的javascript,從而操作這個(gè)組件。
    2010-12-12
  • 在Asp.Net Core中使用ModelConvention實(shí)現(xiàn)全局過(guò)濾器隔離

    在Asp.Net Core中使用ModelConvention實(shí)現(xiàn)全局過(guò)濾器隔離

    這篇文章主要介紹了在Asp.Net Core中使用ModelConvention實(shí)現(xiàn)全局過(guò)濾器隔離,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01

最新評(píng)論