Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In ASP.NET Web API 2, ByteRangeStreamContent returns incorrect data when used with a stream from Azure Storage

Given a Range request like this:

curl -r 0-16 https://example.com/api/blob/mobydick.txt -o moby0.txt -D -

We get:

Call me Ishmael.

But a Range request like this:

curl -r 16-32 https://example.com/api/blob/mobydick.txt -o moby1.txt -D -

We still get:

Call me Ishmael.

This is with the following code (edited to remove getting the blob, accounting for requests without range headers, or with an open-ended range, etc):

Stream myBlobStream = await myBlob.OpenReadAsync();
HttpResponseMessage message = Request.CreateResponse(HttpStatusCode.PartialContent);
message.Content = new ByteRangeStreamContent(myBlobStream , range, myBlob.Properties.ContentType);
return message;

In samples I've seen, it doesn't appear to be necessary to manually Seek or set the Position of the stream, because (as I understand it) that should be handled by ByteRangeStreamContent. When I do try to manually set the Position of the stream to the beginning of the Range, the result is inconsistent; sometimes I end up with a one-byte text file, and sometimes with the entire file starting from the beginning of the Range (i.e. so the end of the Range is ignored).

like image 268
Sean Mahan Avatar asked Oct 18 '25 08:10

Sean Mahan


2 Answers

At least for now, I've solved this by returning ByteArrayContent instead of ByteRangeStreamContent.

// Including my setup of the range values this time:
    var range = Request.Headers.Range;

    long chunkLength = 2500000;
    long? beginRange = range.Ranges.First().From;
    long? endRange = range.Ranges.First().To;

    if (endRange == null)
    {
        if ((beginRange + chunkLength) > myBlob.Properties.Length)
        {
            endRange = myBlob.Properties.Length - 1;
        }
        else
        {
            endRange = beginRange + chunkLength;
        }
    }
    var blobStreamPosition = beginRange.Value;

// Set the stream position
    blobStream.Position = blobStreamPosition;

    int bytesToRead = (int)(endRange - blobStreamPosition + 1);

// Using BinaryReader for convenience
    BinaryReader binaryReader = new BinaryReader(blobStream);
    byte[] blobByteArray = binaryReader.ReadBytes(bytesToRead);
    message.Content = new ByteArrayContent(blobByteArray);

// Don't forget that now you have to set the content range header yourself:
    message.Content.Headers.ContentRange = new ContentRangeHeaderValue(blobStreamPosition, endRange.Value, myBlob.Properties.Length);
    message.Content.Headers.ContentType = new MediaTypeHeaderValue(myBlob.Properties.ContentType);

    binaryReader.Dispose();
    blobStream.Dispose();

I honestly don't know what issues might be lurking in this solution; if nothing else, that byte array means that it should probably include a limit on the size of partial response it'll return. I'd rather use the ByteRangeStreamContent, but this seems to be working for us.

like image 135
Sean Mahan Avatar answered Oct 20 '25 21:10

Sean Mahan


We're having the same problem here.

It seems that WebAPI 2 uses a different version of System.Net.Http than the standard framework library. If we specifically use version 4.0.0.0 of System.Net.Http then using ByteRangeStreamContent works fine. When using 4.2.0.0 (the version with WebAPI 2), the stream always starts from the beginning, whatever the range, and also outputs too many bytes (according to Fiddler).

To get around the issue we have added

<dependentAssembly>
        <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.2.0.0" newVersion="4.0.0.0" />      
</dependentAssembly>

To the <runtime> attribute in <configuration> in the Web.config.

like image 44
Drak Avatar answered Oct 20 '25 22:10

Drak