ASP.NET Core MVC中通过端点实现文件下载的问题排查
问题:Ajax触发文件下载端点无反应,但直接访问地址栏可正常下载
背景
我需要实现用户点击页面ZIP链接后,通过ASP.NET Core后端端点触发文件下载,初始代码如下:
初始后端代码
public class DownloadInput { public int controlId { get; set; } } [Route("/Account/Download")] public async Task<IActionResult> Download(DownloadInput downloadInput) { using (WebClient wc = new WebClient()) { using(var ctx = new BulletdbContext()) { TblControl control = await ctx.TblControls.Where(x => x.Id == downloadInput.controlId).FirstOrDefaultAsync(); string url = _appEnvironment.WebRootPath + "\\jquery_plugins\\" + control.Guid + "\\" + control.Guid + "_" + control.ZipLocation; wc.DownloadFile(url, control.Guid + "_" + control.ZipLocation); } return new EmptyResult(); } }
前端触发代码
通过Ajax请求触发下载:
$(document).on("click", ".account-tab-orders-download", function () { var controlId = $(this).attr("id").split("_")[1]; AjaxCall(function (data) { var t = ""; }, "/Account/Download", function () { }, { controlId: parseInt(controlId) }); })
问题排查过程
- 点击按钮后无下载触发,路径正确但无异常提示。
- 修改后端返回
FileStreamResult,仍无法下载:
public class DownloadInput { public int controlId { get; set; } } [Route("/Account/Download")] public async Task<FileStreamResult> Download(DownloadInput downloadInput) { using (WebClient wc = new WebClient()) { using(var ctx = new BulletdbContext()) { TblControl control = await ctx.TblControls.Where(x => x.Id == downloadInput.controlId).FirstOrDefaultAsync(); string url = _appEnvironment.WebRootPath + "\\jquery_plugins\\" + control.Guid + "\\" + control.Guid + "_" + control.ZipLocation; var stream = System.IO.File.OpenRead(url); return new FileStreamResult(stream, "application/octet-stream"); } } }
- 改为返回
VirtualFileResult时,出现FileNotFoundException,但复制异常中的路径到资源管理器可正常打开文件:
[Route("/Account/Download")] public async Task<VirtualFileResult> Download(DownloadInput downloadInput) { using (var ctx = new BulletdbContext()) { TblControl control = await ctx.TblControls.Where(x => x.Id == downloadInput.controlId).FirstOrDefaultAsync(); string url = _appEnvironment.WebRootPath + "\\jquery_plugins\\" + control.Guid + "\\" + control.Guid + "_" + control.ZipLocation; try { var stream = System.IO.File.OpenRead(url); return File(url, "text/plain", "downloadfile.js"); } catch (Exception e) { return null; } } }
异常信息:
System.IO.FileNotFoundException: Could not find file: C:\Users\tp_re\source\repos\BulletControls\BulletControls\wwwroot\jquery_plugins\caecbb68-0d48-4a1a-953e-c60dc471190d\caecbb68-0d48-4a1a-953e-c60dc471190d_product_carousel.zip. File name: 'C:\Users\tp_re\source\repos\BulletControls\BulletControls\wwwroot\jquery_plugins\caecbb68-0d48-4a1a-953e-c60dc471190d\caecbb68-0d48-4a1a-953e-c60dc471190d_product_carousel.zip'
- 最后修改后端返回文件字节数组,服务器端调试正常,但按钮点击时Chrome网络面板显示
Failed to load response data,直接在地址栏访问端点可正常下载:
[Route("/Account/Download")] public async Task<IActionResult> Download(DownloadInput downloadInput) { using (var ctx = new BulletdbContext()) { TblControl control = await ctx.TblControls.Where(x => x.Id == downloadInput.controlId).FirstOrDefaultAsync(); string url = _appEnvironment.WebRootPath + "\\jquery_plugins\\" + control.Guid + "\\" + control.Guid + "_" + control.ZipLocation; if (System.IO.File.Exists(url)) { byte[] fileBytes = System.IO.File.ReadAllBytes(url); return File(fileBytes, "application/zip", control.Guid + "_" + control.ZipLocation); } } return new EmptyResult(); }
需求
点击UI文件名触发.account-tab-orders-download事件后,服务器响应并自动下载文件。
解决方案
问题核心是Ajax请求无法直接触发浏览器的文件下载行为,因为Ajax响应会被JavaScript接管,浏览器不会自动处理二进制文件的下载逻辑。以下是两种可行方案:
方案1:替换Ajax为直接跳转/新窗口打开
修改前端点击事件,放弃Ajax,直接构造URL触发浏览器默认下载行为:
$(document).on("click", ".account-tab-orders-download", function () { var controlId = $(this).attr("id").split("_")[1]; // 方式1:当前页面跳转下载 window.location.href = "/Account/Download?controlId=" + parseInt(controlId); // 方式2:新窗口打开下载(不影响当前页面) // window.open("/Account/Download?controlId=" + parseInt(controlId), "_blank"); })
这种方式最直接,和直接在地址栏访问端点的效果完全一致。
方案2:处理Ajax响应,手动触发下载
若必须使用Ajax(比如需要传递复杂参数、携带特定请求头),可让后端返回文件Base64编码,前端接收后创建Blob并触发下载:
后端修改
[Route("/Account/Download")] public async Task<IActionResult> Download(DownloadInput downloadInput) { using (var ctx = new BulletdbContext()) { TblControl control = await ctx.TblControls.Where(x => x.Id == downloadInput.controlId).FirstOrDefaultAsync(); string url = _appEnvironment.WebRootPath + "\\jquery_plugins\\" + control.Guid + "\\" + control.Guid + "_" + control.ZipLocation; if (System.IO.File.Exists(url)) { byte[] fileBytes = System.IO.File.ReadAllBytes(url); return Json(new { FileBase64 = Convert.ToBase64String(fileBytes), FileName = control.Guid + "_" + control.ZipLocation, ContentType = "application/zip" }); } } return BadRequest("文件不存在"); }
前端修改
$(document).on("click", ".account-tab-orders-download", function () { var controlId = $(this).attr("id").split("_")[1]; $.ajax({ url: "/Account/Download", type: "GET", data: { controlId: parseInt(controlId) }, success: function (response) { // 解码Base64为Blob var byteCharacters = atob(response.FileBase64); var byteNumbers = new Array(byteCharacters.length); for (var i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } var byteArray = new Uint8Array(byteNumbers); var blob = new Blob([byteArray], { type: response.ContentType }); // 创建临时链接触发下载 var link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = response.FileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(link.href); }, error: function () { alert("下载失败"); } }); })
补充:VirtualFileResult异常修复
之前的FileNotFoundException是因为VirtualFileResult需要相对于Web根目录的虚拟路径,而非本地绝对路径。若要使用该方式,需构造虚拟路径:
// 从wwwroot开始的虚拟路径 string virtualPath = "/jquery_plugins/" + control.Guid + "/" + control.Guid + "_" + control.ZipLocation; return File(virtualPath, "application/zip", control.Guid + "_" + control.ZipLocation);
内容的提问来源于stack exchange,提问作者redoc01
相关产品推荐
相关产品推荐

