如何让 ASP.NET 页面知道文件何时可以从服务器下载

How to let a ASP.NET page know when a file is ready for download from the server

我正在维护旧版 VB.Net Webforms 应用程序,添加一个部分后我遇到了一个奇怪的问题。

这是 aspx 页面中的这段代码,它在执行回发时显示 giphy.gif:

<style type="text/css">
    .modalWait
    {
        position: fixed;
        top: 0;
        left: 0;
        background-color: black;
        z-index: 99;
        opacity: 0.5;
        filter: alpha(opacity=80);
        -moz-opacity: 0.8;
        min-height: 100%;
        width: 100%;
    }
    .loading
    {
        font-family: Arial;
        font-size: 10pt;
        /*border: 5px solid #67CFF5;*/
        width: 100px;
        height: 100px;
        display: none;
        position: fixed;
        background-color: transparent;
        z-index: 999;
    }
</style>

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript">
    function ShowProgress() {
        setTimeout(function () {
            var modal = $('<div />');
            modal.addClass("modalWait");
            $('body').append(modal);
            var loading = $(".loading");
            loading.show();
            var top = Math.max($(window).height() / 2 - loading[0].offsetHeight / 2, 0);
            var left = Math.max($(window).width() / 2 - loading[0].offsetWidth / 2, 0);
            loading.css({ top: top, left: left });
        }, 200);
    }
    $('form').live("submit", function () {
        ShowProgress();
    });
</script>

<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<form runat="server">
    <asp:ScriptManager runat="server"></asp:ScriptManager>

        <!-- Page Wrapper -->
        <div id="wrapper">

        <!-- More Code -->

        </div>
        <!-- End of Page Wrapper -->

        <div class="loading" align="center">
            <%--Loading. Please wait.<br /><br />--%>
            <img src="../img/giphy.gif" />
        </div>

</form>

并且非常适合填充控件的所有查询 - .gif 在调用数据库时出现,然后消失。

但后来我添加了一个函数来将数据网格读入.csv,然后下载它。它工作完美,除非我在后面的代码中添加此部分:

Dim bytes As Byte() = Encoding.ASCII.GetBytes(sb.ToString())

Response.Clear()
Response.ContentType = "text/csv"
Response.AddHeader("Content-Length", bytes.Length.ToString())
Response.AddHeader("Content-disposition", "attachment; filename=contacts.csv")
Response.Write(sb.ToString())
Response.Flush()
Response.End()

文件下载完美...但 giphy.gif 仍然存在...即使回发已完成,它也不会消失。

我做错了什么?

您正在下载文件 (contacts.csv) UI 永远不会更新,因为服务器一次只能发送一种类型的响应(文件或 html 页面),因此图片在下载后仍然可见。

要解决此问题,请在新浏览器中打开下载 window。

<a href="/DownloadFile.aspx" target="_blank">Download CSV</a>

<a href="/handler1.ashx?file=45" target="_blank">Download CSV</a>

如果下载必须在 PostBack 之后进行,则上述解决方案将不起作用。一个可能的解决方案是在文件的平均下载时间后隐藏图像。

function ShowProgress() {

    //rest of code

    setTimeout(function () {
        loading.hide();
    }, 2000);
}

正如 VDWWD 解释的那样 ("the UI is never updated because the server can only send one type of response at a time (a file or a html page)") 你真正的问题是你不知道什么时候 文件下载完成(或者确切地说,当准备文件的 server-side 代码完成执行时)。

关于这个 here on SO 有很多问题,令人惊讶的是,答案几乎总是 你无法知道

嗯,不完全是!

您始终可以通过发回包含时间戳的唯一命名 cookie 来让客户端知道。在客户端,javascript 代码尝试检测 cookie 的存在,当它检测到时,它会隐藏您显示的任何加载图像(或文本或其他内容)。

所以,事不宜迟,这里是代码(我只使用了 loading css class 来使示例更简单和更小):

<form id="form1" runat="server">        
    <div class="loading">
        Loading. Please wait.<br />
        <br />
    </div>
    <asp:HiddenField ID="windowid" runat="server" />        
    <asp:Button ID="Button1" runat="server" Text="Download" 
        OnClick="Button1_Click" 
        OnClientClick="ShowProgress();" />
</form>

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>  

<script type="text/javascript">
    function ShowProgress() {           
        $(".loading").show();
        checkCookie();
    }

    function checkCookie() {
        var cookieVal = $.cookie($('#<%= windowid.ClientID %>').val());
        if (cookieVal == null || cookieVal === 'undefined') {
            setTimeout("checkCookie();", 1000);
        }
        else {
            $(".loading").hide();
        }
    }  
</script>

Code-behind VB.NET:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    If Not IsPostBack Then
        ' the hidden value will be used to uniquely name the cookie and
        ' will be used both on the server and the client side to
        ' work with the cookie.
        windowid.Value = Guid.NewGuid().ToString()
    End If
End Sub

Protected Sub Button1_Click(sender As Object, e As EventArgs)
    ' for demo purposes only
    System.Threading.Thread.Sleep(4000)

    GetCsv()
End Sub

Protected Sub GetCsv()
    ' ... 

    Dim bytes As Byte() = Encoding.ASCII.GetBytes(sb.ToString())

    Response.Clear()
    Response.Cookies.Add(New HttpCookie(windowid.Value, DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss:ff")))
    Response.AddHeader("Content-Disposition", "attachment; filename=contacts.csv")
    Response.AddHeader("Content-Length", bytes.Length.ToString())
    Response.ContentType = "text/csv"
    Response.Write(sb.ToString())
    Response.Flush()
    Response.End()
End Sub

Code-behind C#:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        // the hidden value will be used to uniquely name the cookie and
        // will be used both on the server and the client side to
        // work with the cookie.
        windowid.Value = Guid.NewGuid().ToString();
    }
}


public void GetCsv()
{
    // ...    
    var bytes = Encoding.ASCII.GetBytes(sb.ToString());

    Response.Clear();
    Response.Cookies.Add(new HttpCookie(windowid.Value, DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss:ff")));
    Response.AddHeader("Content-Disposition", "attachment; filename=contacts.csv");
    Response.AddHeader("Content-Length", bytes.Length.ToString());
    Response.ContentType = "text/csv";
    Response.Write(sb.ToString());
    Response.Flush();
    Response.End();
}

protected void Button1_Click(object sender, EventArgs e)
{
    // for demo purposes only
    System.Threading.Thread.Sleep(2000);    

    GetCsv();
}

希望对您有所帮助。如果确实如此,我建议我们编辑问题的标题(也许还有一些内容),以帮助其他人最终找到解决一直以来都没有得到真正回答的问题的可行解决方案。