MoonKnight
MoonKnight

Reputation: 23831

DownloadRangeToStreamAsync and File Save using ASP.NET and MVC5

I am attempting to download a file from Azure Storage in the form of an CloudBlockBlob. I want to allow the user to select where to put the downloaded file, so I have written the following code to do this

[AllowAnonymous]
public async Task<ActionResult> DownloadFile(string displayName)
{
    ApplicationUser user = null;
    if (ModelState.IsValid)
    {
        user = await UserManager.FindByIdAsync(User.Identity.GetUserId());

        // Retrieve storage account and blob client.
        CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
            ConfigurationManager.AppSettings["StorageConnectionString"]);
        CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
        CloudBlobContainer container = blobClient.GetContainerReference(
            VisasysNET.Utilities.Constants.ContainerName);

        // If the container does not exist, return error.
        if (container.Exists())
        {
            foreach (IListBlobItem item in container.ListBlobs(null, false))
            {
                if (item.GetType() == typeof(CloudBlockBlob))
                {
                    CloudBlockBlob blob = (CloudBlockBlob)item;
                    if (blob.Name.CompareNoCase(displayName))
                    {
                        using (Stream stream = new MemoryStream())
                        {
                            await blob.DownloadRangeToStreamAsync(stream, null, null);
                            return File(stream, displayName);
                        }
                    }
                }
            }
            return RedirectToAction("Index", "Tools");
        }
    }
    return new HttpStatusCodeResult(HttpStatusCode.ServiceUnavailable);
}

This idea was to return the FileStreamResult and allow the browser to provide the "Save Dialog". However, when the controller posts to the view, I get an

Cannot access a closed Stream. Exception Details: System.ObjectDisposedException: Cannot access a closed Stream.

Now the problem is clear, however, how to circumvent it is not. I want to ensure the MemoryStream is released and disposed as it is likely to hold data up to ~10M. How can I dispose the MemoryStream but allow the FileStreamResult to be passed back and used by the browser?

Thanks for your time.

Upvotes: 1

Views: 1761

Answers (1)

b2zw2a
b2zw2a

Reputation: 2693

Simply don't dispose it. Have look at FileStreamResult implementation:

 protected override void WriteFile(HttpResponseBase response) {
            // grab chunks of data and write to the output stream
            Stream outputStream = response.OutputStream;
            using (FileStream) {
                byte[] buffer = new byte[_bufferSize];

                while (true) {
                    int bytesRead = FileStream.Read(buffer, 0, _bufferSize);
                    if (bytesRead == 0) {
                        // no more data
                        break;
                    }

                    outputStream.Write(buffer, 0, bytesRead);
                }
            }
        }

FileStreamResult will dispose your stream after writing it to response.OutputStream;

If you don't have to proxy your downloads through the website (security) you can generate Shared Access Signature and redirect your user straight to the storage. See here

Upvotes: 1

Related Questions