8

I writing my filter that running after a sever method call and print its content to Console. The code is written in ASP.NET core v2.1:

public class MyCustomFilter : ActionFilterAttribute
{
    public override void OnResultExecuted(ResultExecutedContext context)
    {

        // ERROR on the next line!
        using (StreamReader sr = new StreamReader(context.HttpContext.Response.Body))
        {
            Console.WriteLine(sr.ReadToEnd());
        }

        base.OnResultExecuted(context);
    }
}

The result - exception:

Stream was not readable.

Further investigation leads me to the point that the stream (context.HttpContext.Response) has those values:

  1. CanRead = false
  2. CanSeek = false

That can explain why it cant read the body...

How to solve?

No1Lives4Ever
  • 6,430
  • 19
  • 77
  • 140

3 Answers3

3

Not sure why you need to do this . context.Result is an instance of IActionResult , you can manipulate it as you like . If you do want to read the Response.Body , there's something hacky can be done.

Since the default Response.Body is not a readable Stream , in order to make the body readable , we need to hijack the response , namely replace the Body with our own instance of Stream :

  1. We can create a brand new memory stream dynamically before action is executing , and hijack the default Response.Body stream .
  2. When action executed , read the stream using a StreamReader, do some work, and set the Response.Body=your new stream .

It's safe to hijack the Response.Body with a plain memory stream because the type of Body is plain Stream.

public class MyCustomFilter : ActionFilterAttribute
{
    private MemoryStream responseBody ;

    public override void OnActionExecuting(ActionExecutingContext context){
        this.responseBody=new MemoryStream();
        // hijack the real stream with our own memory stream 
        context.HttpContext.Response.Body = responseBody;
    }

    public override void OnResultExecuted(ResultExecutedContext context)
    {

        responseBody.Seek(0, SeekOrigin.Begin);

        // read our own memory stream 
        using (StreamReader sr = new StreamReader(responseBody))
        {
            var actionResult= sr.ReadToEnd();
            Console.WriteLine(actionResult);
            // create new stream and assign it to body 
            // context.HttpContext.Response.Body = ;
        }

        // no ERROR on the next line!

        base.OnResultExecuted(context);
    }
}

For a testing purpose , I create an action method :

[MyCustomFilter]
public IActionResult Index()
{
    return Ok("it wooooooooorks");
}

enter image description here

itminus
  • 23,772
  • 2
  • 53
  • 88
  • 4
    No need to fiddle around with your own implementations. Just call `Request.EnableRewind();` (ASP.NET Core 2.0 and later) and the `Request.Stream` will be replaced with a rewindable version. As with your solution, people should always be aware of the memory/performance implications of doing so: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.internal.bufferinghelper.enablerewind?view=aspnetcore-2.1 – Tseng Oct 17 '18 at 11:38
  • @Tseng I didn't realize that . Thanks very much , your solution is much better than mine. – itminus Oct 17 '18 at 12:33
  • @Tseng Hi, sorry for disturbing you. But I cannot figure out how to enable rewind for `Response` like `Request.EnableRewind()`. Could you please do me a favor ? Thanks . – itminus Nov 13 '18 at 11:00
  • 1
    Just by calling `Request.EnableRewind()`, but you have to do it **before** anything has been read from the Request. If some other middleware has started to read from it, its too late to call `EnableRewind()`. `EnableRewind()` is an extension method (as seen in the linked documentation) and you need to declare the namespace import for it to work: `using Microsoft.AspNetCore.Http.Internal;`. But use it with a bit of care, its a so-called "pubinternal" (internal API that has public accessor), means may be subject for (breaking) changes in future – Tseng Nov 13 '18 at 14:47
  • @Tseng Thanks for you very detailed explanation. I just tried with an ` app.Use(async (context , next)=>{context.Request.EnableRewind();await next();});` before any other middlewares, however the `context.Response.Body` is still not readable. Does I miss something ? And I find this [answer](https://stackoverflow.com/a/43404745/10091607) . Will the `Request.EnableRewind()` apply for `context.Response.Body`? – itminus Nov 14 '18 at 01:12
  • 9
    EnableRewind, or EnableBuffering how it is now released as, is only useable for a Request, not for a Response. – Jeroen-bart Engelen Aug 27 '19 at 12:22
  • @itminus this almost solves my problem! Please can you very kindly show how to create new stream and assign it to body? – PKCS12 Oct 23 '20 at 15:49
  • Can someone please show me how to restore the original body? I tried the code above but the response is now empty on browser side. Thank you! – PKCS12 Oct 23 '20 at 16:04
3

Use Actionfilter instead of middleware:

var resultnext = await next();
var objectResult = resultnext.Result as ObjectResult;
var resultValue = objectResult.Value;

            
May Alvarado
  • 111
  • 1
  • 4
0

It all depends on what do you want to achieve. If you want to get the response values or just to see the result you can use

context.Result or context.Result.Value

If you want to modify the response or just to log the entire response you should use a middleware.

here is a good example https://exceptionnotfound.net/using-middleware-to-log-requests-and-responses-in-asp-net-core/

hope it helps

Erez.L
  • 77
  • 1
  • 10