1024programmer Asp.Net Thoughts caused by ASP.NET Core reading Response.Body

Thoughts caused by ASP.NET Core reading Response.Body

Thoughts caused by ASP.NET Core reading Response.Body

Foreword

A few days ago, a group of friends asked in the group how to solve some questions about my previous article “ASP.NET Core WebApi Return Results Unified Packaging Practice”. The main question was about the reading of Respouse. In the previous article “In-depth exploration of the correct way for ASP.NET Core to read Request.Body”, I have analyzed the reading problem of Request. Scenarios that need to read Response are also often encountered, such as reading output information or packaging the output. Results etc. Coincidentally, the reading of Response also has similar problems. In this article, we will analyze how to read the Body of Response.

How to use

How do we read streams in daily use? It’s very simple, just use StreamReader to read as follows

public override void OnResultExecuted(ResultExecutedContext context)
 {
     //Restore the operation bit before operating the stream
     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);
 }
 

The code is very simple, just read it directly, but there is a problem with reading this way and an exception will be thrown System.ArgumentException: "Stream was not readable."The exception information means that the current The Stream is unreadable, that is, the Body of the Respouse cannot be read. Regarding the relationship between StreamReader and Stream, we have analyzed the source code in our previous article, which deeply explores the correct way for ASP.NET Core to read Request.Body. We will not go into details here. Interested students can read it by themselves. Strongly It is recommended that you read that article before reading this article to make it easier to understand.
How to solve the above problem? The method is also very simple. For example, if you want to ensure that the Body of the Response is readable in your program, you can define a middleware to solve this problem.

public static IApplicationBuilder UseResponseBodyRead(this IApplicationBuilder app)
 {
     return app.Use(async (context, next) =>
     {
         //Get the original Response Body
         var originalResponseBody = context.Response.Body;
         try
         {
             //Declare a MemoryStream to replace the Response Body
             using var swapStream = new MemoryStream();
             context.Response.Body = swapStream;
             await next(context);
             //Reset flag bit
             context.Response.Body.Seek(0, SeekOrigin.Begin);
             //Copy the replaced Response Body to the original Response Body
             await swapStream.CopyToAsync(originalResponseBody);
         }
         finally
         {
             //Regardless of whether there is an exception or not, the original Body must be switched back.
             context.Response.Body = originalResponseBody;
         }
     });
 }
 

The essence is to first replace the default ResponseBody with an operable Stream such as our MemoryStream, so that subsequent operations on the ResponseBody will be performed on the new ResponseBody. After completion, the replaced ResponseBody is copied to the original ResponseBody. In the end, regardless of whether it is abnormal or not, the original Body must be switched back. It should be noted that the position of this middleware should be registered at a relatively front position as much as possible, or at least ensure that it is registered before all operations on the ResponseBody. As shown below

var app = builder.Build();
 app.UseResponseBodyRead();
 

Source code exploration

Through the above we learned that ResponseBody cannot be read. As for why, we need to understand this through the relevant source code. We can see the relevant definitions through the source code of the HttpContext class

public abstract class HttpContext
 {
     public abstract HttpResponse Response { get; }
 }
 

Here we see that HttpContext itself is an abstract class. Take a look at its attributes. The definition of the HttpResponse class is also an abstract class

public abstract class HttpResponse
 {
 }
 

It can be seen from the above that the Response attribute is abstract, so the abstract class HttpResponse must contain a subclass to implement it, otherwise there is no way to directly operate the related methods. Here we introduce a website https://source.dot.net that can be used to more easily read the source code of Microsoft class libraries, such as CLR, ASP.NET Core, EF Core, etc. Double-click a class or attribute method to find references and It is very convenient to define them. Its source code is the latest version, and the source is the relevant warehouse on GitHub. Find the instantiation of HttpResponse in the DefaultHttpContext subclass of HttpContext [Click to view the source code👈]

sync method
         WriteAsync(buffer, offset, count, default).GetAwaiter().GetResult();
     }

     public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
     {
         //Essentially calls the writing method of HttpResponsePipeWriter
         return _pipeWriter.WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).GetAsTask();
     }
 )
 

From the above we can see that the HttpResponseStream‘s Write() method essentially calls the HttpResponsePipeWriter‘s WriteAsync() method, HttpResponseStream itself does not store the written data. The construction of the HttpResponsePipeWriter instance is in the BodyControl class. We have pasted the instantiated source code above. You can read it by yourself to see the HttpResponsePipeWriter class. related to the definition. Therefore, the ResponseBody is replaced with MemoryStream, and the final result must be reflected in the HttpResponseStream instance, otherwise there will be no way to output it normally. You can use a pseudocode example to demonstrate this principle

Order order1 = new Order
 {
     Address = "Haidian District, Beijing"
 };

 SetOrder(order1);

 Console.WriteLine($"Last address:{order1.Address}");

 public void SetOrder(Order order2)
 {
     order2 = new Order
     {
         Address = "Minhang District, Shanghai"
     };
     Console.WriteLine($"Set address:{order2.Address}");
 }
 

In this example, even if a new Address is set in the SetOrder method, after leaving the scope of the SetOrder method, the last address outside is still Beijing. Haidian District. When calling SetOrder to enter the method, both order1 and method parameter order2 point to Address = "Haidian District, Beijing". After the instantiation is completed inside the SetOrder method, order2 points to is Address = "Minhang District, Shanghai", but order1 still points to Address = "Haidian District, Beijing", because the pass-by-reference parameter itself is just a stored reference. Address, if you change the reference address, it will be decoupled from the original address. If you want the internal and external behaviors to always be reflected in the original value. The same is true when we replace ResponseBody. In the end, the essence of Write still depends on the HttpResponsePipeWriter attribute in HttpResponseStream, but MemoryStreamThere is no HttpResponsePipeWriter. You may have questions, I didn’t put the MemoryStream result Write() into HttpResponseStream? However, the CopyToAsync method is used above to interact with the original ResponseBody type HttpResponseStream. The essence of the CopyToAsync method is to call the WriteAsync() method. By directly uploading the code [click to view the source code👈], the core code is as follows

public virtual Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
 {
     //Omit part of the code

     return Core(this, destination, bufferSize, cancellationToken);

     static async Task Core(Stream source, Stream destination, int bufferSize, CancellationToken cancellationToken)
     {
         //Used object pool reuse space
         byte[] buffer = ArrayPool.Shared.Rent(bufferSize);
         try
         {
             int bytesRead;
             while ((bytesRead = await source.ReadAsync(new Memory(buffer), cancellationToken).ConfigureAwait(false)) != 0)
             {
                 //Ultimately it is also the WriteAsync method of the target stream that is called
                 await destination.WriteAsync(new ReadOnlyMemory(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false);
             }
         }
         finally
         {
             ArrayPool.Shared.Return(buffer);
         }
     }
 }
 

Summary

This article mainly explains how to read ResponseBody. It cannot be read by default. We need to use middleware combined with MemoryStream to handle it ourselves. At the same time, we After comparing it with the processing method in Http logging middleware, I finally answered the question. In order to continue to reflect the replacement result on the original ResponseBody, overall this aspect is relatively easy. I understand, but it may be troublesome to find. Briefly summarize

  • ResponseBody is not readable by default because its instance is HttpResponseStream. This class overrides the Read-related methods of Stream, but the implementation throws an exception, so we need a readable class. To replace the default operation, MemoryStream can assist in implementation.
  • UseHttpLogging The middleware can also read the results in the ResponseBody, but it uses the Write-related method of overriding the Stream. In the Write method, the Buffer is used to record the written data, and then Read the content in the Buffer through the GetString() method to record the value to be output.
  • MemoryStream solves the problem of reading or writing the ResponseBody during the process of writing code, but after the program is processed, the results of MemoryStream must be reflected in HttpResponseStream, otherwise although there is no problem reading and writing the Body in the program, there will be problems with the output results.

As an aside, the release of ChatGTP had a huge impact on people’s hearts, because its powerful effect was eye-catching, and many bloggers and companies also took advantage of it. Looking for new ways out, some may even worry about being replaced and unemployed. I personally believe that the popularity of new technologies will inevitably bring about new industries, and new industries and new jobs will also require more people to participate. So stay curious about new things and get involved. Tools will not replace people, but people who can use tools can replace people.

👇Please scan the QR code to follow my official account👇
m solves the problem of reading or writing the ResponseBody during the process of writing code, but after the program is processed, the results of MemoryStream must be reflected in HttpResponseStream, otherwise although there is no problem reading and writing the Body in the program, there will be problems with the output results.

As an aside, the release of ChatGTP had a huge impact on people’s hearts, because its powerful effect was eye-catching, and many bloggers and companies also took advantage of it. Looking for new ways out, some may even worry about being replaced and unemployed. I personally believe that the popularity of new technologies will inevitably bring about new industries, and new industries and new jobs will also require more people to participate. So stay curious about new things and get involved. Tools will not replace people, but people who can use tools can replace people.

👇Please scan the QR code to follow my official account👇

This article is from the internet and does not represent1024programmerPosition, please indicate the source when reprinting:https://www.1024programmer.com/thoughts-caused-by-asp-net-core-reading-response-body/

author: admin

Previous article
Next article

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact Us

Contact us

181-3619-1160

Online consultation: QQ交谈

E-mail: [email protected]

Working hours: Monday to Friday, 9:00-17:30, holidays off

Follow wechat
Scan wechat and follow us

Scan wechat and follow us

Follow Weibo
Back to top
首页
微信
电话
搜索