Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading the Response.Body stream in Filter

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?

like image 921
No1Lives4Ever Avatar asked Oct 17 '18 07:10

No1Lives4Ever


2 Answers

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

like image 82
itminus Avatar answered Oct 20 '22 06:10

itminus


Use Actionfilter instead of middleware:

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

            
like image 33
May Alvarado Avatar answered Oct 20 '22 06:10

May Alvarado