How to reset PipeReader to be able to re-read request body in ASP.NET Core
Asked Answered
I

3

8

I have some ASP.NET Core middleware (an AuthorizationHandler) that needs to access the body of the request to perform an authorization check. I use the new pipelines APIs to access the request body. The code looks like this:

PipeReader bodyReader = httpContext.Request.BodyReader;

ReadResult readResult = await bodyReader.ReadAsync();
ReadOnlySequence<byte> readResultBuffer = readResult.Buffer;

var utf8JsonReader = new Utf8JsonReader(bytes);
while (utf8JsonReader.Read()) { ... }

When this code has run, the body stream is read. The controller that I want to call throws a validation error because it doesn't see a request body because it was already read.

So how do I reset the PipeReader so that the request body can be re-read?

I know that when you do not use BodyReader but Body, you can use EnableBuffering to enable request re-reads. However, when using pipelines, this no longer works (or I'm doing something else wrong).

Inflatable answered 17/2, 2020 at 20:2 Comment(0)
R
4

I create an issue yesterday. See jkotalik's comment :

By passing in readResult.Buffer.Start for consumed, you are saying that you haven't consumed any of the ReadResult. By passing in readResult.Buffer.End, you are saying you have examined everything, which tells the Pipe to not free any buffers and the next time ReadAsync returns, more data would be in the buffer

This inspires me to invoke bodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.Start); to reset the examined position.

For example, your middleware can be written as below :

async Task IMiddleware.InvokeAsync(HttpContext httpContext, RequestDelegate next)
{
    if(httpContext.Request.Method != HttpMethods.Post){
        return;
    }
    var bodyReader = httpContext.Request.BodyReader; // don't get it by `PipeReader.Create(httpContext.Request.Body);`
 
    // demo:  read all the body without consuming it
    ReadResult readResult;
    while(true){
        readResult = await bodyReader.ReadAsync();
        if(readResult.IsCompleted) { break; }
        // don't consume them, but examine them
        bodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
    }
    // now all the body payload has been read into buffer
    var buffer = readResult.Buffer; 
    Process(ref buffer);              // process the payload (ReadOnlySequence<byte>)

    // Finally, <b>reset the EXAMINED POSITION</b> here
    bodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.Start);
    await next(httpContext);
}

void Process(ref ReadOnlySequence&lt;byte&gt; buffer){
    var utf8JsonReader = new Utf8JsonReader(buffer);
    while (utf8JsonReader.Read()) {  
        Console.WriteLine($"{utf8JsonReader.TokenType}");
        ...
    }
}

(The Key is the AdvanceTo() can not only forward the consumed position, but also can change the examined position)

Runofthemill answered 19/2, 2020 at 4:32 Comment(3)
Hmm, I tried but doesn't seem to work. The controller still complains it did not receive any JSON tokens...Inflatable
@RonaldWildenberg Could you please show your code? My above code works fine for me.Runofthemill
Sorry, code is long gone and was never pushed because I couldn't get it to work...Inflatable
T
1

I've worked on reading and resetting the request body in a controller's OnActionExecuting method. This is what worked for me:

httpContext.Request.Body.Position = 0;

Maybe this does also work in a pipeline?

Trichinosis answered 18/2, 2020 at 9:19 Comment(1)
Nope, doesn't work when using System.IO.Pipelines unfortunately...Inflatable
A
1

Easiest solution that can be applied in Middleware, for instance: (tested on .Net Core 5)

        context.Request.EnableBuffering();
        //var formParseSuccessful = context.Request.Body read....
        context.Request.Body.Seek(0, SeekOrigin.Begin);
Always answered 12/10, 2021 at 12:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.