shadeglare
shadeglare

Reputation: 7536

Stream S3 file via .NET FileStreamResult and Web API

I've got a code snippet that is supposed to download an S3 file using file streaming:

public async Task<FileStreamResult> DownloadReportFileAsFileStream(String fileName)
{
    var request = new GetObjectRequest { BucketName = this.BucketName, Key = fileName };
    using var response = await this.S3Client.GetObjectAsync(request);
    var (mimeType, originalName) = response.Metadata.ExtractKnownMetaData();
    return new FileStreamResult(response.ResponseStream, mimeType) { FileDownloadName = originalName };
}

Seems legit but somehow this code throws an exception. Here's the top fragment of the stack trace:

System.NotSupportedException:  The ReadAsync method cannot be called when another read operation is pending.
         at System.Net.Security.SslStream.ReadAsyncInternal[TIOAdapter](TIOAdapter adapter, Memory`1 buffer)
         at System.Net.Http.HttpConnection.ReadAsync(Memory`1 destination)

What is strange that if I read an s3 response stream into a memory everything works fine (except handling file content in memory at once):

// FileStorage method to read the file data as a byte array.
public async Task<Byte[]> DownloadReportFileAsByteArray(String fileName)
{
    var request = new GetObjectRequest { BucketName = this.BucketName, Key = fileName };
    using var response = await this.S3Client.GetObjectAsync(request);
    await using var stream = response.ResponseStream;
    await using var memory = new MemoryStream();
    await stream.CopyToAsync(memory);
    return memory.ToArray();
}
...
// returns a FileContentResult object from the file byte array.
public async Task<FileContentResult> DownloadReportS3(DownloadReportRequest request)
{
    var bytes = await this.ReportFileStorage.DownloadReportFileAsByteArray(request.Id);
    var (mimeType, originalName) = await this.ReportFileStorage.GetReportFileMetaData(request.Id);
    return new FileContentResult(bytes, new MediaTypeHeaderValue(mimeType))
    {
        FileDownloadName = originalName
    };
}

Is there a reason why the streaming approach doesn't work correctly? A workaround would be very appriciated.

Upvotes: 3

Views: 2263

Answers (1)

shadeglare
shadeglare

Reputation: 7536

I should not use the using statement for the response object:

public async Task<FileStreamResult> DownloadReportFileAsFileStream(String fileName)
{
    var request = new GetObjectRequest { BucketName = this.BucketName, Key = fileName };
    var response = await this.S3Client.GetObjectAsync(request);
    var (mimeType, originalName) = response.Metadata.ExtractKnownMetaData();
    return new FileStreamResult(response.ResponseStream, mimeType) { FileDownloadName = originalName };
}

Seems FileStreamResult will handle disposing of the stream internally.

Upvotes: 4

Related Questions