I am trying to switch my ASP.NET application using .Net 6 from Stream to PipeReader as recommended by Microsoft. Here is my custom method:
private static async Task<byte[]> GetRequestBodyBytesAsync(PipeReader reader)
{
byte[] bodyBytes;
do
{
ReadResult readResult = await reader.ReadAsync();
if (readResult.IsCompleted || readResult.IsCanceled)
{
ReadOnlySequence<byte> slice = readResult.Buffer.Slice(readResult.Buffer.Start, readResult.Buffer.End);
bodyBytes = BuffersExtensions.ToArray(slice);
reader.AdvanceTo(readResult.Buffer.End);
break;
}
} while (true);
return bodyBytes;
}
However when I use the above static method in my controller:
[HttpPost]
[Route(MyUrl)]
public async Task MyPostAsync()
{
byte[] bodyBytes = await GetRequestBodyBytesAsync(Request.BodyReader);
MyProtobuf myProtobuf = MyProtobuf.Parser.ParseFrom(bodyBytes);
then I can see in the debugger that the readResult.IsCompleted
is never true and the readResult.Buffer
stays the same 21 bytes.
Adding else { reader.AdvanceTo(readResult.Buffer.Start); }
has not changed anything.
I need the complete byte array, so that I can pass it to Protobuf parsing method (there are no streaming parsers for protobuf)... how can I please use IO.Pipelines here?
UPDATE:
The following method (found in PipeReaderExtensions.cs and confirmed by Marc) works now for me and it even can be improved further by avoiding copying data around - see the great comments by Marc below.
private static async Task<byte[]> GetRequestBodyBytesAsync(PipeReader reader)
{
do
{
ReadResult readResult = await reader.ReadAsync();
if (readResult.IsCompleted || readResult.IsCanceled)
{
return readResult.Buffer.ToArray();
}
// consume nothing, keep reading from the pipe reader until all data is there
reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
} while (true);
}
CodePudding user response:
You are meant to call AdvanceTo
after every ReadAsync
, so you should really have an else
that does something like:
reader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
This says "I've checked everything, and consumed nothing" (at least semantically; it doesn't matter that you haven't really looked at anything - the point is to say "all of these bytes: aren't useful to me yet") - which means that ReadAsync
now shouldn't try to give you anything else until it has some additional data or the data has ended. I'm a little surprised that it didn't throw an exception already, actually.