Reputation: 32104
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
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<byte> 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
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
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