Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET: How to convert an Azure RetriableStreamImpl into an ASP.Net Core IFormFile?

I have files stored in Azure Storage in the form of blobs, one blob per file. Files sizes can go up to ~100MB in size.

I am implementing an API that serves these files to consumers as instances of ASP.Net Core 3.1's IFormFile interface.

In my server-side implementation of the API I am downloading the files using Azure's BlobClient.DownloadAsync() method. This returns an instance of BlobDownloadInfo that contains all the data that I need. Specifically, the file's contents are made available through the BlobDownloadInfo.Value.Content property which, at runtime, contains an instance of RetriableStreamImpl

I am mapping from the BlobDownaldInfo into an IFormFile using the default implementation FileInfo class like so:

BlobDownloadInfo response = await blobClient.DownloadAsync();

IFormFile file = new FormFile(
    response.Value.Content, 
    0, // baseStreamOffset
    response.Value.ContentLength, 
    "foo", 
    "bar.txt")
{
    Headers = new HeaderDictionary(),
    ContentType = response.Value.ContentType,
    ContentDisposition = response.Value.Details.ContentDisposition
};

Unfortunately for me, this code throws :

System.NotSupportedException : Specified method is not supported.
   at Azure.Core.Pipeline.RetriableStream.RetriableStreamImpl.set_Position(Int64 value)

It looks to me as though the FormFile constructor is trying to set the stream Position to 0 but that that stream is a RetriableStreamImpl which does not allow setting its Position property.

I've worked around this issue by copying the RetriableStreamImpl contents into a MemoryStream and passing that into the FormFile constructor:

var memoryStream = new MemoryStream();
response.Value.Content.CopyTo(memoryStream);

This works and my question is - Is this a good solution? Are there any performance issues I need to address now?

like image 736
urig Avatar asked Jan 18 '26 06:01

urig


1 Answers

Copying the file to a memory stream is a bad idea. It will slow down the whole process and if many downloads are happening at the same time, you will overload your system's memory.

Instead, you should try to "pass through" the stream from blob storage to the client. We usually do this using FileStreamResult and blob.OpenReadAsync. blobClient.DownloadAsync looks fine for me, so you could try replacing IFile with FileStreamResult.

Some code to get you started:

var blob = container.GetBlockBlobReference(path);
var stream = blob.OpenReadAsync(cancellationToken);

// ...

// Controller action:
return new FileStreamResult(stream, contentType)
 {
     FileDownloadName = "myfile.txt" // => ContentDisposition Header
 }
like image 167
Alex AIT Avatar answered Jan 19 '26 20:01

Alex AIT