我有一个允许用户下载动态生成文件的页面。生成它需要很长时间,所以我想显示一个“等待”指示器。问题是,我不知道如何检测浏览器何时接收到文件,以便我可以隐藏指示器。

我请求一个隐藏的表单,它post到服务器,并以一个隐藏的iframe作为其结果的目标。这样我就不会用结果替换整个浏览器窗口。我监听iframe上的“load”事件,希望它在下载完成时触发。

我将文件返回一个“Content-Disposition: attachment”头,这将导致浏览器显示“Save”对话框。但是浏览器不会在iframe中触发“load”事件。

我尝试过的一种方法是采用多部分回答。所以它会发送一个空的HTML文件,以及附加的可下载文件。

例如:

Content-type: multipart/x-mixed-replace;boundary="abcde"

--abcde
Content-type: text/html

--abcde
Content-type: application/vnd.fdf
Content-Disposition: attachment; filename=foo.fdf

file-content
--abcde

这在Firefox中是有效的;它接收空HTML文件,触发“load”事件,然后显示可下载文件的“Save”对话框。但它在ie和Safari浏览器上无法运行;Internet Explorer触发“load”事件,但它不下载文件,而Safari下载文件(带有错误的名称和内容类型),但不触发“load”事件。

另一种方法可能是调用启动文件创建,轮询服务器直到准备就绪,然后下载已经创建的文件。但我宁愿避免在服务器上创建临时文件。

我该怎么办?


当前回答

根据我的经验,有两种方法可以解决这个问题:

Set a short-lived cookie on the download, and have JavaScript continually check for its existence. Only real issue is getting the cookie lifetime right - too short and the JavaScript can miss it, too long and it might cancel the download screens for other downloads. Using JavaScript to remove the cookie upon discovery usually fixes this. Download the file using fetch/XHR. Not only do you know exactly when the file download finishes, if you use XHR you can use progress events to show a progress bar! Save the resulting blob with msSaveBlob in Internet Explorer or Edge and a download link (like this one) in Firefox and Chrome. The problem with this method is that iOS Safari doesn't seem to handle downloading blobs right - you can convert the blob into a data URL with a FileReader and open that in a new window, but that's opening the file, not saving it.

其他回答

问题是在生成文件时有一个“等待”指示器,然后在文件下载后恢复正常。我喜欢这样做的方式是使用一个隐藏的iFrame和钩子帧的onload事件,让我的页面知道什么时候开始下载。

但是onload不会在Internet Explorer中触发文件下载(就像使用附件头令牌一样)。轮询服务器是可行的,但我不喜欢这种额外的复杂性。这就是我所做的:

目标隐藏iFrame像往常一样。 生成内容。用 2分钟内绝对超时。 发送JavaScript重定向回 调用客户端,本质上调用 第二次生成页面。注意:这将导致在Internet Explorer中触发onload事件,因为它就像一个普通页面一样。 从缓存中删除内容并 发送给客户端。

免责声明:不要在繁忙的站点上这样做,因为缓存会增加。但实际上,如果您的站点非常繁忙,那么长时间运行的进程无论如何都会耗尽您的线程。

下面是隐藏代码的样子,这是您真正需要的。

public partial class Download : System.Web.UI.Page
{
    protected System.Web.UI.HtmlControls.HtmlControl Body;

    protected void Page_Load( object sender, EventArgs e )
    {
        byte[ ] data;
        string reportKey = Session.SessionID + "_Report";

        // Check is this page request to generate the content
        //    or return the content (data query string defined)
        if ( Request.QueryString[ "data" ] != null )
        {
            // Get the data and remove the cache
            data = Cache[ reportKey ] as byte[ ];
            Cache.Remove( reportKey );

            if ( data == null )
                // send the user some information
                Response.Write( "Javascript to tell user there was a problem." );
            else
            {
                Response.CacheControl = "no-cache";
                Response.AppendHeader( "Pragma", "no-cache" );
                Response.Buffer = true;

                Response.AppendHeader( "content-disposition", "attachment; filename=Report.pdf" );
                Response.AppendHeader( "content-size", data.Length.ToString( ) );
                Response.BinaryWrite( data );
            }
            Response.End();
        }
        else
        {
            // Generate the data here. I am loading a file just for an example
            using ( System.IO.FileStream stream = new System.IO.FileStream( @"C:\1.pdf", System.IO.FileMode.Open ) )
                using ( System.IO.BinaryReader reader = new System.IO.BinaryReader( stream ) )
                {
                    data = new byte[ reader.BaseStream.Length ];
                    reader.Read( data, 0, data.Length );
                }

            // Store the content for retrieval
            Cache.Insert( reportKey, data, null, DateTime.Now.AddMinutes( 5 ), TimeSpan.Zero );

            // This is the key bit that tells the frame to reload this page
            //   and start downloading the content. NOTE: Url has a query string
            //   value, so that the content isn't generated again.
            Body.Attributes.Add("onload", "window.location = 'binary.aspx?data=t'");
        }
    }

这个解决方案非常简单,但很可靠。并且它可以显示真实的进度消息(并且可以轻松地插入到现有流程中):

处理的脚本(我的问题是:通过HTTP检索文件并将其作为ZIP传递)将状态写入会话。

该状态每秒轮询一次。这就是全部(好吧,这不是。您必须注意很多细节(例如,并发下载),但这是一个开始的好地方;-))。

下载页面:

<a href="download.php?id=1" class="download">DOWNLOAD 1</a>
<a href="download.php?id=2" class="download">DOWNLOAD 2</a>

...

<div id="wait">
    Please wait...
    <div id="statusmessage"></div>
</div>

<script>

    // This is jQuery
    $('a.download').each(function()
    {
        $(this).click(
            function() {
                $('#statusmessage').html('prepare loading...');
                $('#wait').show();
                setTimeout('getstatus()', 1000);
            }
            );
        });
    });

    function getstatus() {
        $.ajax({
            url: "/getstatus.php",
            type: "POST",
            dataType: 'json',
            success: function(data) {
                $('#statusmessage').html(data.message);
                if(data.status == "pending")
                    setTimeout('getstatus()', 1000);
                else
                    $('#wait').hide();
                }
        });
    }
</script>

文件getstatus.php

<?php
    session_start();
    echo json_encode($_SESSION['downloadstatus']);
?>

文件download.php

<?php
    session_start();
    $processing = true;
    while($processing) {
        $_SESSION['downloadstatus'] = array("status" =>"pending", "message" => "Processing".$someinfo);
        session_write_close();
        $processing = do_what_has_2Bdone();
        session_start();
    }

    $_SESSION['downloadstatus'] = array("status" => "finished", "message" => "Done");
    // And spit the generated file to the browser
?>

我也遇到过同样的问题。我的解决方案是使用临时文件,因为我已经生成了一堆临时文件。提交表格时:

var microBox = {
    show : function(content) {
        $(document.body).append('<div id="microBox_overlay"></div><div id="microBox_window"><div id="microBox_frame"><div id="microBox">' +
        content + '</div></div></div>');
        return $('#microBox_overlay');
    },

    close : function() {
        $('#microBox_overlay').remove();
        $('#microBox_window').remove();
    }
};

$.fn.bgForm = function(content, callback) {
    // Create an iframe as target of form submit
    var id = 'bgForm' + (new Date().getTime());
    var $iframe = $('<iframe id="' + id + '" name="' + id + '" style="display: none;" src="about:blank"></iframe>')
        .appendTo(document.body);
    var $form = this;
    // Submittal to an iframe target prevents page refresh
    $form.attr('target', id);
    // The first load event is called when about:blank is loaded
    $iframe.one('load', function() {
        // Attach listener to load events that occur after successful form submittal
        $iframe.load(function() {
            microBox.close();
            if (typeof(callback) == 'function') {
                var iframe = $iframe[0];
                var doc = iframe.contentWindow.document;
                var data = doc.body.innerHTML;
                callback(data);
            }
        });
    });

    this.submit(function() {
        microBox.show(content);
    });

    return this;
};

$('#myForm').bgForm('Please wait...');

在生成文件的脚本的末尾:

header('Refresh: 0;url=fetch.php?token=' . $token);
echo '<html></html>';

这将导致iframe上的load事件被触发。然后关闭等待消息,然后开始文件下载。它在Internet Explorer 7和Firefox上进行了测试。

当按钮/链接被单击时创建一个iframe,并将其附加到body。

$('<iframe />')
    .attr('src', url)
    .attr('id', 'iframe_download_report')
    .hide()
    .appendTo('body');

创建一个延迟的iframe,下载后删除。

var triggerDelay =   100;
var cleaningDelay =  20000;
var that = this;
setTimeout(function() {
    var frame = $('<iframe style="width:1px; height:1px;" class="multi-download-frame"></iframe>');
    frame.attr('src', url + "?" + "Content-Disposition: attachment ; filename=" + that.model.get('fileName'));
    $(ev.target).after(frame);
    setTimeout(function() {
        frame.remove();
    }, cleaningDelay);
}, triggerDelay);

如果你不想在服务器上生成和存储文件,你是否愿意存储状态,例如文件进行中,文件完成?您的“等待”页面可以轮询服务器以了解文件生成何时完成。您不能确定是浏览器启动了下载,但您有一定的信心。