Downloading Azure Blob files in MVC3 Downloading Azure Blob files in MVC3 azure azure

Downloading Azure Blob files in MVC3


Two options really... the first is to just redirect the user to the blob directly (if the blobs are in a public container). That would look a bit like:

return Redirect(container.GetBlobReference(name).Uri.AbsoluteUri);

If the blob is in a private container, you could either use a Shared Access Signature and do redirection like the previous example, or you could read the blob in your controller action and push it down to the client as a download:

Response.AddHeader("Content-Disposition", "attachment; filename=" + name); // force downloadcontainer.GetBlobReference(name).DownloadToStream(Response.OutputStream);return new EmptyResult();


Here's a resumable version (useful for large files or allowing seek in video or audio playback) of private blob access:

public class AzureBlobStream : ActionResult{    private string filename, containerName;    public AzureBlobStream(string containerName, string filename)    {        this.containerName = containerName;        this.filename = filename;        this.contentType = contentType;    }    public override void ExecuteResult(ControllerContext context)    {        var response = context.HttpContext.Response;        var request = context.HttpContext.Request;        var connectionString = ConfigurationManager.ConnectionStrings["Storage"].ConnectionString;        var account = CloudStorageAccount.Parse(connectionString);        var client = account.CreateCloudBlobClient();        var container = client.GetContainerReference(containerName);        var blob = container.GetBlockBlobReference(filename);        blob.FetchAttributes();        var fileLength = blob.Properties.Length;        var fileExists = fileLength > 0;        var etag = blob.Properties.ETag;        var responseLength = fileLength;        var buffer = new byte[4096];        var startIndex = 0;        //if the "If-Match" exists and is different to etag (or is equal to any "*" with no resource) then return 412 precondition failed        if (request.Headers["If-Match"] == "*" && !fileExists ||            request.Headers["If-Match"] != null && request.Headers["If-Match"] != "*" && request.Headers["If-Match"] != etag)        {            response.StatusCode = (int)HttpStatusCode.PreconditionFailed;            return;        }        if (!fileExists)        {            response.StatusCode = (int)HttpStatusCode.NotFound;            return;        }        if (request.Headers["If-None-Match"] == etag)        {            response.StatusCode = (int)HttpStatusCode.NotModified;            return;        }        if (request.Headers["Range"] != null && (request.Headers["If-Range"] == null || request.Headers["IF-Range"] == etag))        {            var match = Regex.Match(request.Headers["Range"], @"bytes=(\d*)-(\d*)");            startIndex = Util.Parse<int>(match.Groups[1].Value);            responseLength = (Util.Parse<int?>(match.Groups[2].Value) + 1 ?? fileLength) - startIndex;            response.StatusCode = (int)HttpStatusCode.PartialContent;            response.Headers["Content-Range"] = "bytes " + startIndex + "-" + (startIndex + responseLength - 1) + "/" + fileLength;        }        response.Headers["Accept-Ranges"] = "bytes";        response.Headers["Content-Length"] = responseLength.ToString();        response.Cache.SetCacheability(HttpCacheability.Public); //required for etag output        response.Cache.SetETag(etag); //required for IE9 resumable downloads        response.ContentType = blob.Properties.ContentType;        blob.DownloadRangeToStream(response.OutputStream, startIndex, responseLength);    }}

Example:

Response.AddHeader("Content-Disposition", "attachment; filename=" + filename); // force downloadreturn new AzureBlobStream(blobContainerName, filename);


I noticed that writing to the response stream from the action method messes up the HTTP headers. Some expected headers are missing and others are not set correctly.

So instead of writing to the response stream, I get the blob content as a stream and pass it to the Controller.File() method.

CloudBlockBlob blob = container.GetBlockBlobReference(blobName);Stream blobStream = blob.OpenRead();return File(blobStream, blob.Properties.ContentType, "FileName.txt");