根据许多在线教程和示例,我构建了一个基于客户端上的jQuery和服务器上的MS WebAPI构建的文件下载系统。
无法提供与文件的直接链接,因为API需要身份验证,因此文件URL是API端点,而不是文件位置。
在服务器上我有这个:
[HttpGet]
[Route("download/{filename}")]
public HttpResponseMessage DownloadFile(string filename)
{
try
{
// https://gist.github.com/joeriks/3714093
string path = string.Format("{0}/Exports/{1}", root, filename);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "download.txt";
return result;
}
catch (Exception ex)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
}
哪个会按预期返回响应:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 4809
Content-Type: application/octet-stream
Expires: -1
Server: Microsoft-IIS/10.0
Content-Disposition: attachment; filename=download.txt
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, PUT, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: content-Type, accept, origin, X-Requested-With, X-Authentication, X-Nonce, name
Date: Thu, 25 Oct 2018 13:07:25 GMT
在响应中带有文本内容。 到目前为止,一切都如我所愿。
在客户端上,我有以下处理来自我的API的响应:
// https://stackoverflow.com/a/23797348
let disposition = jqXHR.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
let filename = "scada-download.txt";
let matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
let type = jqXHR.getResponseHeader('Content-Type');
let blob = new Blob([data], { type: "text/csv" });
var downloadUrl = URL.createObjectURL(blob);
let $a = $("<a id='temp_download_link' style='display: none;' />").attr("href", downloadUrl).attr("download", filename);
$("body").append($a);
$a.trigger("click");
}
此操作与广告一样,并在页面上添加了锚点,然后单击该页面触发下载。
下载有效,并保存内容正确的文件。
唯一不起作用的地方是,在我已经在(Chrome 69,FF:62)中测试过的两种浏览器中,提供的默认文件名只是一个GUID。
api和客户端代码目前都在我的本地开发机上运行,http://127.0.0.1:9000/[client | api]
,因此跨源不应该发挥任何作用。
通过ajax调用api。最终,jQuery $ .ajax()方法
为清楚起见,插入DOM的锚点是:
<a id="temp_download_link" style="display: none;" href="blob:http://127.0.0.1:9000/c2c5ffb5-3f22-4a57-8775-4e0bbfbfef9e" download="download.txt"></a>
Chrome提供的默认文件名是URL中的GUID,FF会生成看似未连接的6个字符的随机字符串。
具体来说,
为什么浏览器同时忽略锚点的download="download.txt"
属性和Content-Disposition: attachment; filename=download.txt
属性?
更新:
我分叉了这个小提琴:
并为href属性添加了其他一些值,似乎整个设置文件名充其量都是:
更新2
我已将链接从一个工作示例复制到我的小提琴中,它在原始站点中有效,但在小提琴中无效。
http://jsfiddle.net/yubjqwvs/2/
我觉得答案可以归结为“为什么它在David Walsh Blog上有效,但在小提琴上无效?”
答案 0 :(得分:0)
我个人使用downloadjs来避免类似的问题:
Content-Disposition: attachment; filename="..."
)的PHP脚本根据您的示例,downloadjs的代码与jsfiddle非常相似。可以在Firefox 63,Opera 56(我猜它应该在Chrome上运行)上正常运行,并且在IE 11上无法运行,但是好吧,那就是IE。
click()
,这可能会调用本机处理程序。使用您的代码,并使用click()
以及document.getElementById()
而不是jQuery,给我们这个jsfiddle。与downloadjs相反,我不使用window.setTimeout
,它至少在Fx 63中运行良好。这可能只是绕过浏览器错误的一种方式。
在您的情况下,我的猜测是,当您调用trigger('click')
无法理解时,jQuery不会触发默认处理程序,因为trigger
文档似乎告诉我们确实如此:
从jQuery 1.3开始,.trigger()ed事件使DOM树冒泡;一个事件 处理程序可以通过从处理程序返回false来停止冒泡,或者 在传入的事件对象上调用.stopPropagation()方法 事件。尽管.trigger()模拟了事件激活,但完成 使用合成的事件对象,它不能完美地复制 自然发生的事件。
要触发通过jQuery绑定的处理程序,而不会同时触发 本机事件,请改用.triggerHandler()。 (引自jquery documentation)
也许默认的链接点击不被认为是jquery的处理程序(?),并且不会被触发。
答案 1 :(得分:0)
我看到的原因是您使用的是jQuery .attr()函数而不是 .prop()函数。 HTML5属性和属性之间存在区别。第一个在事件绑定等情况下为元素提供标记,而另一个则访问/设置DOM元素本身的值。
我将链接一个可能有用的解释here。
答案 2 :(得分:0)
当修复它变得至关重要时,我终于回到了这个问题。
事实证明,我的(本地的)SPA框架是问题所在,因为它覆盖了<a />
我通过添加一个blob链接来捕获此错误,然后触发默认的click事件:
// convert all a/href to a#href
$("body").delegate("a", "click", function () {
let href: string = $(this).attr("href");
// check its not an external / absolute URL
let regex: RegExp = new RegExp("^(blob:)?(http|https)(:\/\/)", "ig");
let match = regex.exec(href);
if (match) {
// match[0] is the full match, match[1] is lookign for "blob:"
// it will either be undefined or blob:
if (match[1]) { // its a blob url, call the default
return true; // !! this line is the core of the fix !!
}
else { // load a normal link
// see if its got a target.
let target: string = $(this).attr("target");
switch (target) {
default: document.location.href = href; break;
case "_blank": window.open(href); break;
}
}
}
else
// SPA stuff
return false;
});
从本质上讲,除非他们像我这样愚蠢到足以扮演自己的SPA框架的角色,否则没人会遇到这个问题。