通过AJAX调用ASP.NET Core控制器获取Zip文件时文件无效问题
问题:ASP.NET Core控制器生成的Zip文件通过AJAX返回后无法正常解压
我在Stack Overflow及网络上搜索多日,试过各种ASP.NET Core控制器生成Zip文件并通过AJAX返回给客户端的示例,但下载的Zip文件始终无效。
AJAX请求会提交Zip文件需包含的内容数据,服务器通过HTTP调用从内容管理系统获取图片并压缩为Zip文件。我在JS里把返回的数据转成Blob,再用window.URL.createObjectURL处理后设置到弹窗的锚点供用户下载。已经验证过服务器端生成的Zip数据写入磁盘后是有效的,问题出在前端处理环节。
前端AJAX代码
$.ajax({ url: downloadUrl, type: 'post', data: "searchText=".concat(modelData).concat("&source=").concat(target.id), }).done(function (data, status, xhr) { var contentType = xhr.getResponseHeader("Content-Type"); var blob = new Blob([data], { type: contentType }); // 设置Blob到下载链接 var aDownload = document.getElementById('download-file'); aDownload.href = window.URL.createObjectURL(blob); aDownload.download = "downloadfile.zip" // 弹出下载窗口 document.getElementById("embed-popup").hidden = true; const embedCode = document.getElementById('embed-popup'); embedCode.value = target.dataset.embed; document.body.classList.add('show-embed'); requestAnimationFrame(() => { autosize.update(embedCode); }); });
后端控制器代码
[HttpPost] public async Task<IActionResult> ResourcesDownload(ResourceSearch resourceSearch) { ResourceSearchResult resourceResult = JsonConvert.DeserializeObject<ResourceSearchResult>(resourceSearch.SearchText); string[] resourceSearchSplits = resourceSearch.Source.Split('|'); SubCategory subCategory = resourceResult.SubCategories.SingleOrDefault(x => x.Name == resourceSearchSplits.Last()); using (MemoryStream zipStream = new MemoryStream()) { using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true)) { foreach (Resource resource in subCategory.Resources) { string fileSuffix = resource.FileType.Split('/')[1]; string title = Regex.Replace(resource.Title, "[^a-zA-Z0-9]", String.Empty); ZipArchiveEntry entry = zip.CreateEntry($"{title}.{fileSuffix}"); using (HttpClient httpClient = new HttpClient()) { using (Stream stream = await httpClient.GetStreamAsync(new Uri(resource.FileUrl))) { using (Stream entryStream = entry.Open()) { await stream.CopyToAsync(entryStream); } } } } } zipStream.Position = 0; return File(zipStream.ToArray(), "application/zip", "downloadfile.zip"); } }
操作流程
- 点击链接触发AJAX请求,提交Zip应包含的资源信息:

- 服务器返回Zip数据后弹出窗口,用户点击链接即可下载文件:

解决方案
问题核心是AJAX请求未指定responseType,默认情况下jQuery会把二进制Zip数据解析为字符串,导致Blob生成错误,最终文件损坏。
1. 修改AJAX请求,指定响应类型为Blob
$.ajax({ url: downloadUrl, type: 'post', data: "searchText=".concat(modelData).concat("&source=").concat(target.id), xhrFields: { responseType: 'blob' // 关键:告诉浏览器直接处理二进制Blob数据 } }).done(function (blob, status, xhr) { // 直接使用返回的Blob对象,无需二次转换 var aDownload = document.getElementById('download-file'); aDownload.href = window.URL.createObjectURL(blob); aDownload.download = "downloadfile.zip" // 弹窗逻辑保留 document.getElementById("embed-popup").hidden = true; const embedCode = document.getElementById('embed-popup'); embedCode.value = target.dataset.embed; document.body.classList.add('show-embed'); requestAnimationFrame(() => { autosize.update(embedCode); }); });
2. 后端代码优化建议
- 移除未使用的
HttpResponseMessage代码块,直接返回File结果即可 - 复用
HttpClient(不要在循环内重复创建),通过依赖注入IHttpClientFactory提升性能 - 增加空值判断,避免空引用异常
优化后的后端代码示例:
private readonly IHttpClientFactory _httpClientFactory; // 构造函数注入IHttpClientFactory public YourControllerName(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } [HttpPost] public async Task<IActionResult> ResourcesDownload(ResourceSearch resourceSearch) { if (string.IsNullOrEmpty(resourceSearch.SearchText) || string.IsNullOrEmpty(resourceSearch.Source)) { return BadRequest("无效的请求参数"); } ResourceSearchResult resourceResult = JsonConvert.DeserializeObject<ResourceSearchResult>(resourceSearch.SearchText); string[] resourceSearchSplits = resourceSearch.Source.Split('|'); if (resourceSearchSplits.Length == 0) { return BadRequest("Source参数格式错误"); } SubCategory subCategory = resourceResult?.SubCategories?.SingleOrDefault(x => x.Name == resourceSearchSplits.Last()); if (subCategory == null || !subCategory.Resources.Any()) { return NotFound("未找到可下载的资源"); } using (MemoryStream zipStream = new MemoryStream()) { using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true)) { var httpClient = _httpClientFactory.CreateClient(); foreach (Resource resource in subCategory.Resources) { if (string.IsNullOrEmpty(resource.FileUrl) || string.IsNullOrEmpty(resource.FileType)) { continue; } string[] fileTypeParts = resource.FileType.Split('/'); if (fileTypeParts.Length < 2) { continue; } string fileSuffix = fileTypeParts[1]; string title = Regex.Replace(resource.Title, "[^a-zA-Z0-9]", String.Empty); ZipArchiveEntry entry = zip.CreateEntry($"{title}.{fileSuffix}"); using (Stream stream = await httpClient.GetStreamAsync(new Uri(resource.FileUrl))) using (Stream entryStream = entry.Open()) { await stream.CopyToAsync(entryStream); } } } zipStream.Position = 0; return File(zipStream.ToArray(), "application/zip", "downloadfile.zip"); } }
问题原因说明
默认情况下jQuery的AJAX会把响应数据当作UTF-8字符串解析,二进制Zip文件包含大量非UTF-8字符,解析过程中会丢失关键数据,后续转成Blob时已经是损坏的文件。指定responseType: 'blob'后,浏览器会直接保留原始二进制数据,生成的Blob才能正确还原为有效Zip文件。
内容的提问来源于stack exchange,提问作者Eric Wilson
相关产品推荐
相关产品推荐

