携带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
相关产品推荐
相关产品推荐

