You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.NET Core 6 Web API中触发浏览器直接下载文件

问题描述

我正在生成Zip文件并上传至Azure Blob Storage,随后向客户端返回URL,客户端通过该URL(即API端点)触发API以启动下载。目前我生成的是TinyURL,它指向通过Azure SAS Builder生成的实际URL。当访问该TinyURL时,会调用API、查询数据库、获取实际URL并下载文件。我希望通过URI触发浏览器直接下载文件,而非将文件加载到浏览器中。

相关代码
public async Task<JsonResult> GenerateZipUri(FileDownloadRequest[] requests, string userId, string fileSaveAs, CancellationToken ct)
{
    (string uri, string latestRev) = await CheckCustomUriAndLatestRev(userId, ct);
    if (!string.IsNullOrEmpty(uri)) return ApiResult.OKResult(body: uri);
    string tempFilePath = TempFileManager.CreateTempFile("zip");
    try
    {
        await CreateZipFile(requests, tempFilePath);
        await UploadZipFileToBlobStorage(userId, fileSaveAs, tempFilePath, ct);
        string customUrl = await GetCustomUrl(userId, fileSaveAs, latestRev, ct);
        return ApiResult.OKResult(body: customUrl);
    }
    catch (Exception e)
    {
        log.LogError(e, "Exception has been thrown while creating Zip from Azure Blob Storage");
        return ApiResult.InternalServerError(e.Message);
    }
    finally
    {
        TempFileManager.DeleteTempFile(tempFilePath);
    }
}

private async Task<(string customUri, string latestRev)> CheckCustomUriAndLatestRev(string userId, CancellationToken ct)
{
    string docId = $"tinyurl:{userId}";
    DocumentResponse response = await GetCustomUrlByUserId(docId, ct);
    TinyUrl tinyUrl = JsonConvert.DeserializeObject<TinyUrl>(response.Content);
    if (response.StatusCode == HttpStatusCode.OK)
    {
        if (tinyUrl.ExpiryDatetime > DateTime.UtcNow)
        {
            return (tinyUrl.CustomUrl.ToString(), tinyUrl.Rev);
        }
    }
    return (string.Empty, tinyUrl.Rev);
}

private async Task CreateZipFile(FileDownloadRequest[] requests, string tempFilePath)
{
    using FileStream tempFileStream = new(tempFilePath, FileMode.Create);
    await GetZipOfBlobFiles(requests, tempFileStream);
}

private async Task UploadZipFileToBlobStorage(string userId, string fileSaveAs, string tempFilePath, CancellationToken ct)
{
    FileUploadRequest fileUploadRequest = new()
    {
        Container = userId,
        Extension = "zip",
        Name = fileSaveAs,
        OwnerType = "",
        DocumentType = "file",
    };
    BlobContainerClient blobContainerClient = await CreateContainerIfNotExistsAsync(userId);
    string blobName = $"{fileSaveAs}.{fileUploadRequest.Extension}";
    using FileStream tempFileStream = new(tempFilePath, FileMode.Open);
    await UploadBlobAsync(blobContainerClient, tempFileStream, blobName, SetMetaData(fileUploadRequest), ct);
}

private async Task<string> GetCustomUrl(string userId, string fileSaveAs, string latestRev, CancellationToken ct)
{
    BlobClient blobClient = new(AppConfig.BlobStorageConnectionString, userId, $"{fileSaveAs}.zip");
    BlobSasBuilder sasBuilder = CreateBlobSasBuilder(userId, $"{fileSaveAs}.zip");
    TinyUrl doc = new(userId)
    {
        Rev = latestRev,
        ActualUrl = blobClient.GenerateSasUri(sasBuilder),
        ExpiryDatetime = DateTime.UtcNow.AddMinutes(10),
        CustomUrl = CreateURI("/filetransfer/file/downloadfile", userId),
    };
    CouchWrapper couch = new(azureConfig);
    DocumentHeaderResponse headerResponse = await couch.UpsertDocumentAsync(doc.Id, doc.ToJson(), ct);
    return doc.CustomUrl.ToString();
}
private Uri CreateURI(string path, string userId)
{
    UriBuilder uriBuilder = new(AppConfig.BaseUrl)
    {
        Path = path,
        Query = $"{nameof(userId)}={userId}",
    };
    return uriBuilder.Uri;
}

private BlobSasBuilder CreateBlobSasBuilder(string userId, string blobName)
{
    BlobSasBuilder sasBuilder = new()
    {
        BlobContainerName = userId,
        BlobName = blobName,
        Resource = "b",
        StartsOn = DateTime.UtcNow,
        ExpiresOn = DateTime.UtcNow.AddMinutes(10),
        Protocol = SasProtocol.Https,
    };
    sasBuilder.SetPermissions(BlobSasPermissions.Read);
    return sasBuilder;
}
解决方案

要触发浏览器直接下载而非加载文件,核心是设置Content-Disposition响应头为attachment; filename="文件名",以下是三种可行方案:

  • 方案1:上传Blob时添加元数据
    修改SetMetaData方法,为Blob添加Content-Disposition元数据,示例:

    private IDictionary<string, string> SetMetaData(FileUploadRequest request)
    {
        var metaData = new Dictionary<string, string>();
        // 保留原有元数据逻辑
        metaData["Content-Disposition"] = $"attachment; filename=\"{request.Name}.{request.Extension}\"";
        return metaData;
    }
    

    上传后Azure Blob返回文件时会自动携带该头,触发浏览器下载。

  • 方案2:在SAS URL中指定响应头
    在CreateBlobSasBuilder方法中,通过Headers属性添加Content-Disposition,生成的SAS URL会强制返回该头:

    private BlobSasBuilder CreateBlobSasBuilder(string userId, string blobName)
    {
        BlobSasBuilder sasBuilder = new()
        {
            BlobContainerName = userId,
            BlobName = blobName,
            Resource = "b",
            StartsOn = DateTime.UtcNow,
            ExpiresOn = DateTime.UtcNow.AddMinutes(10),
            Protocol = SasProtocol.Https,
        };
        sasBuilder.SetPermissions(BlobSasPermissions.Read);
        // 添加下载触发头
        sasBuilder.Headers.ContentDisposition = $"attachment; filename=\"{blobName}\"";
        return sasBuilder;
    }
    
  • 方案3:在下载API端点处理
    如果无法修改Blob或SAS设置,在/filetransfer/file/downloadfile端点中代理Blob内容并设置响应头:

    public async Task<IActionResult> DownloadFile(string userId, CancellationToken ct)
    {
        // 实现查询数据库获取ActualUrl的逻辑
        using var httpClient = new HttpClient();
        var stream = await httpClient.GetStreamAsync(actualUrl);
        return File(stream, "application/zip", $"存档.zip");
    }
    

    该方法通过API主动控制响应头,触发浏览器下载。

内容的提问来源于stack exchange,提问作者Jug Patel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 07:52:52