下载<href =“ blob:http:url =”“ guid” =“” download =“ filename”>忽略下载属性

时间:2018-10-25 13:29:21

标签: jquery ajax asp.net-web-api download

根据许多在线教程和示例,我构建了一个基于客户端上的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属性?

更新:

我分叉了这个小提琴:

http://jsfiddle.net/Qjvb3/

并为href属性添加了其他一些值,似乎整个设置文件名充其量都是:

http://jsfiddle.net/yubjqwvs/

更新2

我已将链接从一个工作示例复制到我的小提琴中,它在原始站点中有效,但在小提琴中无效。

http://jsfiddle.net/yubjqwvs/2/

我觉得答案可以归结为“为什么它在David Walsh Blog上有效,但在小提琴上无效?”

3 个答案:

答案 0 :(得分:0)

我个人使用downloadjs来避免类似的问题:

  • 发出AJAX请求以下载文件;那只是一个带有适当标头(Content-Disposition: attachment; filename="...")的PHP脚本
  • 用户单击链接,该链接使用hashchange事件触发下载,然后我将AJAX响应传递给downloadjs。我想可以使用Blob URL来完成同样的操作。

根据您的示例,downloadjs的代码与jsfiddle非常相似。可以在Firefox 63,Opera 56(我猜它应该在Chrome上运行)上正常运行,并且在IE 11上无法运行,但是好吧,那就是IE。

  • downloadjs创建一个隐藏链接,其超时设置为66ms(我想这是为了等待DOM准备就绪或类似的操作)。
  • 超时在链接上调用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框架的角色,否则没人会遇到这个问题。

在这里https://github.com/JohnRayson/JSPA