Ronald Wildenberg
Ronald Wildenberg

Reputation: 32104

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

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).

Upvotes: 8

Views: 3694

Answers (3)

itminus
itminus

Reputation: 25360

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)

Upvotes: 4

andrew.fox
andrew.fox

Reputation: 7933

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);

Upvotes: 1

citronas
citronas

Reputation: 19365

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?

Upvotes: 1

Related Questions