简体   繁体   中英

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

I'm maintaining a legacy VB.Net Webforms App and I've got a strange issue after adding one section.

This is this code in the aspx page which shows the giphy.gif while doing a postback:

<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>

And works perfectly for all the queries which populate the controls - the.gif appears while the database is called and then goes away.

But then I added a function to read the datagrid into a.csv and then download it. It works perfectly, except when I add this section in the code behind:

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()

The file downloads perfectly... but the giphy.gif is still there... it doesn't go away even though the postback has finished.

What am I doing wrong?

As VDWWD explained ( "the UI is never updated because the server can only send one type of response at a time (a file or a html page)" ) your real problem is that you have no idea when the file download gets completed (or to be exact, when the server-side code, which prepares the file, has finished executing).

There are more than a handful of questions about this here on SO and amazingly almost always the answer is you cannot know !

Well, not quite!

You can always let the client know by sending back a uniquely named cookie containing a time stamp. On the client, the javascript code tries to detect the cookie presence and when it does it hides any loading image (or text or whatever) you've shown.

So, without further ado here's the code (I only used the loading css class to make the sample simpler and smaller):

<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();
}

I hope this helps. If it does I suggest we edit the title (and maybe some of the content) of the question so as to help others finally find a working solution to a problem that hasn't been really answered all this time.

You are using ScriptManager without UpdatePanel Try to replace (Where start <ScriptManager ……..) with this:

<asp:ScriptManager ID="ScriptManager1" runat="server"/> 
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>


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

        <!-- More Code -->

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


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



    </ContentTemplate>
</asp:UpdatePanel>

You are downloading a file ( contacts.csv ) the UI is never updated because the server can only send one type of response at a time (a file or a html page), so the image will remain visible after download.

To fix this either open the download in a new browser window.

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

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

If the download must come after a PostBack then the above solution will not work. A possible solution would be to just hide the image after the average download time of the file.

function ShowProgress() {

    //rest of code

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM