如何在.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
相关产品推荐
相关产品推荐

