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

携带Bearer Token的Ajax文件下载导致浏览器跳转页面的问题

问题描述

我参考了Stack Overflow上的方案,通过客户端调用服务器端的MVC控制器。由于需要支持Bearer Token,我选择手动编写Ajax请求而非使用fileDownload等库/插件。

目前控制器能被正常触发并返回FileDownloadResult类型的数据:

public sealed class FileDownloadResult : IHttpActionResult, IDisposable 
{ 
    private readonly MemoryStream _dataStream; 
    private readonly string _fileName; 
    private readonly string _path; 
    private readonly string _mimeType; 
    private readonly string _encoding; 

    private string MimeType => string.IsNullOrWhiteSpace(_mimeType) ? "application/octet-stream" : _mimeType; 
    private string Encoding => string.IsNullOrWhiteSpace(_encoding) ? string.Empty : _encoding; 

    public FileDownloadResult(byte[] fileData, string fileName) 
    { 
        _dataStream = new MemoryStream(fileData); 
        _fileName = fileName; 
    } 

    // ...(other overrides methods for FileDownloadResult)... 
}

但使用下述客户端代码时,浏览器会发生页面跳转,调试时发现返回的data类型为document,请问该如何解决此问题?

客户端代码:

function downloadFile(url, data) { 
    $.ajax({ 
        url: url, 
        type: "GET", 
        data: data, 
        headers: { Authorization: 'Bearer ' + appsecurity.getBearerTokenWithoutHeader().accessToken }, 
        success: function (data) { 
            debugger; 
            $("a") 
                .attr({ "href": data.file, "download": "file.txt" }) 
                .html($("a").attr("download")) 
                .get(0).click(); 
            console.log(JSON.parse(JSON.stringify(data))); 
        }, 
        error: function (jqxhr, textStatus, errorThrown) { 
            console.log(textStatus, errorThrown) 
        } 
    }); 
}
解决方案

嘿,这个问题我之前也踩过坑!核心原因是你用Ajax请求文件流时,默认的响应类型是document,浏览器会把返回的二进制数据当成网页文档处理,自然就触发页面跳转了。下面给你几个靠谱的解决办法:

1. 给Ajax指定响应类型为Blob

首先得告诉jQuery,我们要接收的是二进制文件流,而非普通文档。在Ajax配置里加上xhrFields设置responseType为blob,再通过创建临时URL触发下载:

function downloadFile(url, data) { 
    $.ajax({ 
        url: url, 
        type: "GET", 
        data: data, 
        headers: { Authorization: 'Bearer ' + appsecurity.getBearerTokenWithoutHeader().accessToken },
        // 关键配置:指定响应为二进制Blob类型
        xhrFields: {
            responseType: 'blob'
        },
        success: function (blobData) { 
            // 创建临时URL指向Blob数据
            const downloadUrl = URL.createObjectURL(blobData);
            // 生成临时a标签(避免影响页面现有a标签)
            const tempLink = document.createElement('a');
            tempLink.href = downloadUrl;
            tempLink.download = "file.txt"; // 可替换为你需要的文件名
            // 加入页面才能触发点击
            document.body.appendChild(tempLink);
            tempLink.click();
            // 清理资源防止内存泄漏
            URL.revokeObjectURL(downloadUrl);
            document.body.removeChild(tempLink);
        }, 
        error: function (jqxhr, textStatus, errorThrown) { 
            console.log(textStatus, errorThrown) 
        } 
    }); 
}

这样处理后,Ajax会把服务器返回的文件流解析成Blob对象,通过临时URL触发下载,不会导致页面跳转。

2. 从响应头自动获取文件名(优化项)

如果你的服务器返回文件时,能在响应头里设置Content-Disposition(比如attachment; filename=你的文件名.txt),可以自动提取文件名,不用手动写死。修改success回调:

success: function (blobData, textStatus, jqxhr) { 
    // 从响应头获取Content-Disposition
    const contentDisposition = jqxhr.getResponseHeader('Content-Disposition');
    let fileName = "file.txt"; // 默认文件名
    if (contentDisposition) {
        // 解析出头中的文件名
        const nameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
        if (nameMatch && nameMatch[1]) {
            fileName = decodeURIComponent(nameMatch[1].replace(/['"]/g, ''));
        }
    }
    // 后续下载逻辑和之前一致
    const downloadUrl = URL.createObjectURL(blobData);
    const tempLink = document.createElement('a');
    tempLink.href = downloadUrl;
    tempLink.download = fileName;
    document.body.appendChild(tempLink);
    tempLink.click();
    URL.revokeObjectURL(downloadUrl);
    document.body.removeChild(tempLink);
}

当然,这需要你的FileDownloadResult在ExecuteAsync方法里正确设置响应头:

public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
    var response = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StreamContent(_dataStream)
    };
    response.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeType);
    // 关键:设置Content-Disposition头,告知浏览器这是附件
    response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
    {
        FileName = _fileName
    };
    return response;
}

3. 避免直接操作页面现有a标签

你原来的代码直接用$("a")修改属性,会影响页面所有a标签,容易出问题。创建临时a标签用完即删,才是安全的做法,不会干扰页面其他元素。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:59:29