Reputation: 23831
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
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