我在服务器端有一个Struts2操作用于文件下载。

<action name="download" class="com.xxx.DownAction">
    <result name="success" type="stream">
        <param name="contentType">text/plain</param>
        <param name="inputName">imageStream</param>
        <param name="contentDisposition">attachment;filename={fileName}</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

然而,当我使用jQuery调用动作时:

$.post(
  "/download.action",{
    para1:value1,
    para2:value2
    ....
  },function(data){
      console.info(data);
   }
);

在Firebug中,我看到数据是用二进制流检索的。我想知道如何打开文件下载窗口,用户可以在本地保存文件?


当前回答

1. 框架不可知:Servlet下载文件作为附件

<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>

2. Struts2框架:动作下载文件作为附件

<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

最好使用<s:a>标记,用OGNL指向用<s: URL >标记创建的URL:

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

在上述情况下,您需要将Content-Disposition报头写入响应,指定文件需要下载(附件),而不是由浏览器打开(内联)。您还需要指定Content Type,并且您可能希望添加文件名和长度(以帮助浏览器绘制一个真实的进度条)。

例如,当下载ZIP文件时:

response.setContentType("application/zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

使用Struts2(除非您将Action用作Servlet,例如,用于直接流的黑客),您不需要直接向响应写入任何内容;简单地使用Stream结果类型并在struts.xml中配置它就可以了

<result name="success" type="stream">
   <param name="contentType">application/zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3.框架不可知(/ Struts2框架):Servlet(/Action)在浏览器中打开文件

如果你想在浏览器中打开文件,而不是下载它,Content-disposition必须设置为内联,但目标不能是当前窗口位置;你必须目标一个由javascript创建的新窗口,一个<iframe>在页面中,或者一个新窗口创建的飞行与"discussed" target="_blank":

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>

其他回答

我尝试下载一个CSV文件,然后在下载完成后做一些事情。所以我需要实现一个适当的回调函数。

使用window.location="…"不是一个好主意,因为下载完成后我无法操作该程序。像这样,改变头,所以这不是一个好主意。

fetch是一个很好的选择,但是它不支持ie11。window.URL.createObjectURL不支持ie11。你可以参考这个。

这是我的密码,它类似于沙赫鲁克·阿拉姆的密码。但你应该注意window。url。createobjecturl可能会造成内存泄漏。你可以参考这个。当响应到达时,数据将被存储到浏览器的内存中。因此,在单击链接之前,文件已经下载。这意味着下载后你可以做任何事情。

$.ajax({
    url: 'your download url',
    type: 'GET',
}).done(function (data, textStatus, request) {
    // csv => Blob
    var blob = new Blob([data]);

    // the file name from server.
    var fileName = request.getResponseHeader('fileName');

    if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
    window.navigator.msSaveOrOpenBlob(blob, fileName);
    } else { // for others
    var url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);

    //Do something after download 
    ...

    }
}).then(after_download)
}

没有人贴出这个@Pekka的解决方案…所以我会把它贴出来。它可以帮助别人。

您不需要通过Ajax来实现这一点。只使用

window.location="download.action?para1=value1...."

这就是它在任何浏览器中都很好(我使用asp.net核心)

function onDownload() { const api = '@Url.Action("myaction", "mycontroller")'; var form = new FormData(document.getElementById('form1')); fetch(api, { body: form, method: "POST"}) .then(resp => resp.blob()) .then(blob => { const url = window.URL.createObjectURL(blob); $('#linkdownload').attr('download', 'Attachement.zip'); $('#linkdownload').attr("href", url); $('#linkdownload') .fadeIn(3000, function() { }); }) .catch(() => alert('An error occurred')); } <button type="button" onclick="onDownload()" class="btn btn-primary btn-sm">Click to Process Files</button> <a role="button" href="#" style="display: none" class="btn btn-sm btn-secondary" id="linkdownload">Click to download Attachments</a> <form asp-controller="mycontroller" asp-action="myaction" id="form1"></form>

        function onDownload() {
            const api = '@Url.Action("myaction", "mycontroller")'; 
            //form1 is your id form, and to get data content of form
            var form = new FormData(document.getElementById('form1'));

            fetch(api, { body: form, method: "POST"})
                .then(resp => resp.blob())
                .then(blob => {
                    const url = window.URL.createObjectURL(blob);
                    $('#linkdownload').attr('download', 'Attachments.zip');
                    $('#linkdownload').attr("href", url);
                    $('#linkdownload')
                        .fadeIn(3000,
                            function() {

                            });
                })
                .catch(() => alert('An error occurred'));                 

        }

2019现代浏览器更新

这是我现在推荐的方法,但有几点注意事项:

需要一个相对现代的浏览器 如果文件非常大,您可能会采取与原始方法(iframe和cookie)类似的方法,因为以下一些操作可能会消耗至少与正在下载的文件相同大的系统内存和/或其他有趣的CPU副作用。

fetch('https://jsonplaceholder.typicode.com/todos/1') .then(resp => resp.blob()) .then(blob => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; // the filename you want a.download = 'todo-1.json'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); alert('your file has downloaded!'); // or you know, something with better UX... }) .catch(() => alert('oh no!'));

2012年原创jQuery/iframe/Cookie的方法

Bluish在这一点上是完全正确的,你不能通过Ajax做到这一点,因为JavaScript不能直接将文件保存到用户的计算机(出于安全考虑)。不幸的是,将主窗口的URL指向您的文件下载意味着当文件下载发生时,您几乎无法控制用户体验。

我创建了jQuery文件下载,它允许“类似Ajax”的体验,文件下载完成OnSuccess和OnFailure回调,以提供更好的用户体验。看看我的博客文章,关于这个插件解决的常见问题和一些使用它的方法,还有一个jQuery文件下载的演示。这是来源

下面是一个简单的用例演示,使用带有承诺的插件源代码。演示页面还包括许多其他“更好的用户体验”示例。

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });

取决于你需要支持什么浏览器,你可以使用https://github.com/eligrey/FileSaver.js/,它允许比jQuery文件下载使用的IFRAME方法更明确的控制。

这是使用MVC时的工作代码,你从控制器中获取文件

假设你有你的字节数组声明和填充,你唯一需要做的是使用文件函数(使用System.Web.Mvc)

byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");

然后,在同一个控制器中,添加这两个函数

protected override void OnResultExecuting(ResultExecutingContext context)
    {
        CheckAndHandleFileResult(context);

        base.OnResultExecuting(context);
    }

    private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";

    /// <summary>
    /// If the current response is a FileResult (an MVC base class for files) then write a
    /// cookie to inform jquery.fileDownload that a successful file download has occured
    /// </summary>
    /// <param name="context"></param>
    private void CheckAndHandleFileResult(ResultExecutingContext context)
    {
        if (context.Result is FileResult)
            //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
            Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
        else
            //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
            if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
    }

然后你将能够调用你的控制器来下载并获得"success"或"failure"回调

$.fileDownload(mvcUrl('name of the controller'), {
            httpMethod: 'POST',
            successCallback: function (url) {
            //insert success code

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });