0

ASP.NET Core FileStreamResult sometimes fails when writing big blob (~1gb) to response

...
Azure.Storage.Blobs.BlobClient blob = container.GetBlobClient(fileName)
Stream stream = await blob.OpenReadAsync().ConfigureAwait(false);
return File(stream, MediaTypeNames.Application.Octet, fileName); 

Logged error (occurs inside FileStreamResultExecutor):

 System.ArgumentNullException: Value cannot be null. (Parameter 'buffer')
   at System.Net.Http.HttpBaseStream.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
   at Azure.Core.Pipeline.ReadTimeoutStream.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
   at Azure.Core.Pipeline.RetriableStream.RetriableStreamImpl.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
   at Azure.Core.Pipeline.RetriableStream.RetriableStreamImpl.RetryAsync(Exception exception, Boolean async, CancellationToken cancellationToken)
   at Azure.Core.Pipeline.RetriableStream.RetriableStreamImpl.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
   at Azure.Storage.LazyLoadingReadOnlyStream`1.DownloadInternal(Boolean async, CancellationToken cancellationToken)
   at Azure.Storage.LazyLoadingReadOnlyStream`1.ReadInternal(Byte[] buffer, Int32 offset, Int32 count, Boolean async, CancellationToken cancellationToken)
   at Azure.Storage.LazyLoadingReadOnlyStream`1.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.StreamCopyOperationInternal.CopyToAsync(Stream source, Stream destination, Nullable`1 count, Int32 bufferSize, CancellationToken cancel)
   at Microsoft.AspNetCore.Internal.FileResultHelper.WriteFileAsync(HttpContext context, Stream fileStream, RangeItemHeaderValue range, Int64 rangeLength)

Platform: Windows, .NET 6

Linas
  • 560
  • 1
  • 5
  • 16

1 Answers1

0

As per the error message, it is due to buffer is null, when the FileStreamResultExecutor is trying to read from it. You can resolve it by increasing the buffer size.

Another approach is to check if the stream is null before passing it to the FileStreamResult. You can add a null check before returning the FileStreamResult to ensure that the stream is not null.

To check if the stream is null before returning the FileStreamResult

Azure.Storage.Blobs.BlobClient blob = container.GetBlobClient(fileName) Stream stream = await blob.OpenReadAsync().ConfigureAwait(false); if (stream == null) 
{ 
// handle null stream 
} 
return File(stream, MediaTypeNames.Application.Octet,fileName);

You can increase the buffer size by setting the ResponseBufferSize property in the KestrelServerOptions Set the ResponseBufferSize property to 1 GB.

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.ConfigureKestrel(options =>
            {
                options.Limits.MaxRequestBodySize = null;
                options.Limits.MinResponseDataRate = null;
                options.Limits.MinRequestBodyDataRate = null;
                options.Limits.MaxResponseBufferSize = 1073741824; // 1 GB
            });
            webBuilder.UseStartup<Startup>();
        });

Another approach

You can use the PushStreamContent class instead of FileStreamResult to stream the file to the response.

PushStreamContent

public async Task<IActionResult> DownloadFile()
{
    var blob = container.GetBlobClient(fileName);
    var stream = await blob.OpenReadAsync().ConfigureAwait(false);
    var response = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new PushStreamContent(async (outputStream, httpContent, transportContext) =>
        {
            await stream.CopyToAsync(outputStream);
            outputStream.Close();
        })
    };
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
    {
        FileName = fileName
    };
    return new HttpResponseMessageResult(response);
}

For further information refer to the MSDoc.

Rajesh Mopati
  • 1,329
  • 1
  • 2
  • 7
  • This answer make no sense to me. I experience the same error . Per the stack trace, the error happens deep down the Azure Storage library, I have no access to the buffer used there. – Thomas Eyde May 13 '23 at 20:06