JavaScript / jQuery通过POST和JSON数据下载文件

我有一个基于jQuery的单页web应用程序。 它通过AJAX调用与RESTful Web服务进行通信。

我试图完成以下任务:

  • 将包含JSON数据的POST提交给REST网址。
  • 如果请求指定了JSON响应,则返回JSON。
  • 如果请求指定了PDF / XLS / etc响应,则返回可下载的二进制文件。
  • 我现在有1和2工作,客户端jQuery应用程序通过创建基于JSON数据的DOM元素在网页中显示返回的数据。 我也从Web服务的角度来看#3,这意味着如果给出正确的JSON参数,它将创建并返回一个二进制文件。 但我不确定在客户端JavaScript代码中处理#3的最佳方式。

    是否有可能从这样的ajax调用中获得可下载的文件? 如何让浏览器下载并保存文件?

    $.ajax({
        type: "POST",
        url: "/services/test",
        contentType: "application/json",
        data: JSON.stringify({category: 42, sort: 3, type: "pdf"}),
        dataType: "json",
        success: function(json, status){
            if (status != "success") {
                log("Error loading data");
                return;
            }
            log("Data loaded!");
        },
        error: function(result, status, err) {
            log("Error loading data");
            return;
        }
    });
    

    服务器使用以下标题进行响应:

    Content-Disposition:attachment; filename=export-1282022272283.pdf
    Content-Length:5120
    Content-Type:application/pdf
    Server:Jetty(6.1.11)
    

    另一个想法是生成PDF并将其存储在服务器上,并返回包含该文件URL的JSON。 然后,在ajax成功处理程序中发出另一个调用,以执行以下操作:

    success: function(json,status) {
        window.location.href = json.url;
    }
    

    但是这样做意味着我需要对服务器进行多次调用,并且我的服务器需要构建可下载的文件,将它们存储在某个位置,然后定期清理该存储区域。

    必须有一个更简单的方法来实现这一点。 想法?


    编辑:审查$ .ajax的文档后,我看到响应dataType只能是xml, html, script, json, jsonp, text ,所以我猜没有办法直接下载文件使用ajax请求,除非我在@VinayC回答(这不是我想要做的)中建议的嵌入二进制文件使用数据URI方案。

    所以我想我的选择是:

  • 不使用ajax,而是提交表单帖子并将我的JSON数据嵌入到表单值中。 可能需要混淆隐藏的iframe等。

  • 不使用ajax,而是将我的JSON数据转换为查询字符串,以构建标准GET请求并将window.location.href设置为此URL。 可能需要在我的点击处理程序中使用event.preventDefault()以防止浏览器从应用程序URL更改。

  • 使用我上面的其他想法,但增强了@naikus答案的建议。 用一些参数提交AJAX请求,让Web服务知道这是通过ajax调用调用的。 如果Web服务是从ajax调用中调用的,只需将JSON与URL一起返回到生成的资源。 如果直接调用资源,则返回实际的二进制文件。

  • 我越想它,我越喜欢最后的选择。 这样我就可以获得关于请求的信息(生成时间,文件大小,错误消息等),并且我可以在开始下载之前根据这些信息采取行动。 缺点是服务器上的额外文件管理。

    任何其他方式来实现这一目标? 我应该注意到这些方法的优点/缺点?


    letronje的解决方案仅适用于非常简单的页面。 document.body.innerHTML +=获取正文的HTML文本,附加iframe HTML,并将页面的innerHTML设置为该字符串。 这将消除您的页面中包含的任何事件绑定。 创建一个元素,然后使用appendChild

    $.post('/create_binary_file.php', postData, function(retData) {
      var iframe = document.createElement("iframe");
      iframe.setAttribute("src", retData.url);
      iframe.setAttribute("style", "display: none");
      document.body.appendChild(iframe);
    }); 
    

    或者使用jQuery

    $.post('/create_binary_file.php', postData, function(retData) {
      $("body").append("<iframe src='" + retData.url+ "' style='display: none;' ></iframe>");
    }); 
    

    这实际上是这样做的:使用变量postData中的数据执行post到/create_binary_file.php; 如果该帖子成功完成,请将新的iframe添加到页面的正文中。 假设来自/create_binary_file.php的响应将包含一个值'url',该值是生成的PDF / XLS / etc文件可以从中下载的URL。 假设Web服务器具有适当的MIME类型配置,向引用该URL的页面添加iframe将导致浏览器促使用户下载该文件。


    我一直在玩另一个使用blob的选项。 我设法让它下载文本文档,并且我下载了PDF文件(但是它们已经损坏)。

    使用blob API,您将能够执行以下操作:

    $.post(/*...*/,function (result)
    {
        var blob=new Blob([result]);
        var link=document.createElement('a');
        link.href=window.URL.createObjectURL(blob);
        link.download="myFileName.txt";
        link.click();
    
    });
    

    这是IE 10+,Chrome 8+,FF 4+。 请参阅https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL

    它只会在Chrome,Firefox和Opera下载该文件。 这会在锚标签上使用下载属性来强制浏览器下载它。


    我知道这种旧的,但我想我已经想出了一个更优雅的解决方案。 我有同样的问题。 我提出的解决方案的问题是,他们都需要将文件保存在服务器上,但我不想将这些文件保存在服务器上,因为它引入了其他问题(安全性:文件可以被访问未经过身份验证的用户,清理:如何以及何时摆脱文件)。 和你一样,我的数据很复杂,嵌套的JSON对象很难放入表单中。

    我所做的是创建两个服务器功能。 第一个验证了数据。 如果出现错误,它将被退回。 如果它不是错误,我将所有参数序列化/编码为base64字符串。 然后,在客户端上,我有一个只有一个隐藏输入并发布到第二个服务器功能的表单。 我将隐藏的输入设置为base64字符串并提交格式。 第二个服务器功能解码/反序列化参数并生成文件。 表单可以提交给页面上的新窗口或iframe,文件将打开。

    涉及的工作量稍多一些,可能还需要多一点的处理,但总的来说,我觉得这个解决方案更好。

    代码在C#/ MVC中

        public JsonResult Validate(int reportId, string format, ReportParamModel[] parameters)
        {
            // TODO: do validation
    
            if (valid)
            {
                GenerateParams generateParams = new GenerateParams(reportId, format, parameters);
    
                string data = new EntityBase64Converter<GenerateParams>().ToBase64(generateParams);
    
                return Json(new { State = "Success", Data = data });
            }
    
            return Json(new { State = "Error", Data = "Error message" });
        }
    
        public ActionResult Generate(string data)
        {
            GenerateParams generateParams = new EntityBase64Converter<GenerateParams>().ToEntity(data);
    
            // TODO: Generate file
    
            return File(bytes, mimeType);
        }
    

    在客户端

        function generate(reportId, format, parameters)
        {
            var data = {
                reportId: reportId,
                format: format,
                params: params
            };
    
            $.ajax(
            {
                url: "/Validate",
                type: 'POST',
                data: JSON.stringify(data),
                dataType: 'json',
                contentType: 'application/json; charset=utf-8',
                success: generateComplete
            });
        }
    
        function generateComplete(result)
        {
            if (result.State == "Success")
            {
                // this could/should already be set in the HTML
                formGenerate.action = "/Generate";
                formGenerate.target = iframeFile;
    
                hidData = result.Data;
                formGenerate.submit();
            }
            else
                // TODO: display error messages
        }
    
    链接地址: http://www.djcxy.com/p/8047.html

    上一篇: JavaScript/jQuery to download file via POST with JSON data

    下一篇: ajax parseerror