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

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

问题排查过程

  1. 点击按钮后无下载触发,路径正确但无异常提示。
  2. 修改后端返回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");
        }
    }
}
  1. 改为返回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'
  1. 最后修改后端返回文件字节数组,服务器端调试正常,但按钮点击时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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 03:17:32