【问题标题】:Downloading Azure Blob files in MVC3在 MVC3 中下载 Azure Blob 文件
【发布时间】:2025-12-13 15:30:01
【问题描述】:

我们的 ASP.NET MVC 3 应用程序在 Azure 上运行并使用 Blob 作为文件存储。我已经弄清楚了上传部分。

视图将具有文件名,单击该文件名将提示出现文件下载屏幕。

谁能告诉我该怎么做?

【问题讨论】:

    标签: asp.net-mvc asp.net-mvc-3 azure azure-storage azure-blob-storage


    【解决方案1】:

    确实有两个选项...第一个是直接将用户重定向到 blob(如果 blob 在公共容器中)。看起来有点像:

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

    如果 blob 位于私有容器中,您可以使用共享访问签名并像前面的示例一样执行重定向,或者您可以在控制器操作中读取 blob 并将其作为下载推送到客户端:

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

    【讨论】:

    • 这是一个私有 blob,所以我使用了您发布的第二种方法,它完全按照我想要的方式工作。非常感谢!
    • 我想对用户隐藏文件名(并放入我自己的)你知道怎么做吗?
    • 只需在 Content-Disposition 标头中添加任何您想要的内容。
    • @smarx 为什么不能在 azure blob 上设置此标头?
    • @Robotsushi 因为 blob API 不支持它。
    【解决方案2】:

    这是私有 blob 访问的可恢复版本(适用于大文件或允许在视频或音频播放中查找):

    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);
        }
    }
    

    例子:

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

    【讨论】:

    • 知道如何强制结果的 Cache-Control 标头与 blob 的标头相同吗?
    • 什么是Util.Parse
    • @MartinDawson 哎呀应该已经简化了,它来自这个*.com/a/6474962/222748
    【解决方案3】:

    我注意到从 action 方法写入响应流会弄乱 HTTP 标头。缺少一些预期的标头,而其他标头设置不正确。

    因此,我没有写入响应流,而是将 blob 内容作为流获取并将其传递给 Controller.File() 方法。

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

    【讨论】: