Search code examples
c#asp.net-web-apiasp.net-web-api2azure-storageazure-blob-storage

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


Solution

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