문제

I'm building a web application on ASP.NET with C#. On a button click I show a loading image, while the database query is being executed. Then I dynamically create an Excel file and send it to the client like this:

HttpContext.Current.Response.Clear();
HttpContext.Current.Response.AddHeader("content-disposition", "attachment;filename=" + filename + ".xlsx");
HttpContext.Current.Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.Unicode;
HttpContext.Current.Response.BinaryWrite(p.GetAsByteArray());
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End(); 

I get the dialog box, and the loading image stays there.

I've tried placing call to a javascript function (with ClientScript.RegisterStartupScript function) before the code above, it didn't work. As I understand all javascript code is run after all the code-behind has executed, but in this case it doesn't execute at all once the file is sent to the client.

I've also tried creating a separate thread, and removing the loading image there. I put the breakpoint to trace it, code in the thread does execute, but the image still stays there.

Does anyone have an idea how this can be handled? Thank you!

도움이 되었습니까?

해결책

You can only send or transmit 1 mime type in one request/response cycle. (My knowledge in this area is debatable).

That said, you can design a hack around this. Use an iframe on the client to "download the file". You can point its src to an ashx file that does the same.

You need to wire the iframe's onload event, so your web page has someway of knowing that download is done; thats where you can execute your logic.

Solution Update:

Well, after digging around, I've discovered my answer is half-baked!

The issue is that iframes don't trigger their onload event after they download something. The onload event will trigger iff there the url pointed to by src actually navigates to a different page. This is by design I suppose. And I learn that today!

So what then is the work-around?!

Fortunately, you can transmit cookies to the client. On the client your web page has to keep polling for the presence of this cookie. So once your web page is able to detect the presence of the cookie, it means that the browser has completed with the download request. This has been discussed in great detail in the following post:

http://geekswithblogs.net/GruffCode/archive/2010/10/28/detecting-the-file-download-dialog-in-the-browser.aspx

I'll just show you some code relating to the handler file (which simulates a download), and the client (which has an iframe doing the job). This should pretty much give you the gist:

Webform1.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApp.FileDownload.WebForm1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>iFrame Download</title>
    <script type="text/javascript" src="Scripts/jquery-2.1.0.min.js"></script>
    <script type="text/javascript" src="Scripts/jquery.cookie.js"></script>
    <script type="text/javascript">
        function foo() {
            console.log('foo');
            //execute post-download logic here
        }
        $(function () {            
            $('input').click(function () {
                //make sure we get rid of the 
                //cookie before download
                $.removeCookie('downloaded');

                var intrvl = setTimeout(function () { //this function polls for the cookie through which we track that the file has indeed been downloaded
                    console.log('timer');
                    var value = $.cookie('downloaded');
                    if (value == 'true') {
                        clearTimeout(intrvl);
                        foo();
                    }
                }, 1000);

                //this initiates the download
                $('iframe').attr({
                    'src': 'download.ashx?id=' + $('#tbxRandomNumber').val()
                });

            });
        });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:TextBox ID="tbxRandomNumber" runat="server"></asp:TextBox>
        <input type="button" value="Download" />
        <iframe src="about:blank" style="display:none"></iframe>
        <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Next Random Number" />
    </div>
    </form>
</body>
</html>

I've made used of jquery cookies plugin to help me with handling cookies.

download.ashx:

using System;
using System.Web;

namespace WebApp.FileDownload
{
    /// <summary>
    /// Summary description for download
    /// </summary>
    public class download : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {            
            context.Response.ContentType = "text/plain";
            context.Response.SetCookie(new HttpCookie("downloaded","true")); //setting cookie in the response
            string id = context.Request.QueryString["id"] == null ? "NULL" : context.Request.QueryString["id"];
            string str = string.Format("Content with id {0} was generated at {1}", id, DateTime.Now.ToLongTimeString());

            context.Response.AddHeader("Content-Disposition", "attachment; filename=test.txt");
            context.Response.AddHeader("Content-Length", str.Length.ToString());
            context.Response.Write(str);
            context.Response.End();
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

다른 팁

It looks like you have a couple of misunderstandings here. You only have one request, and one response from the server. Making new threads is something that only happens on the server, and won't create additional responses.

When you're sending the Excel file, you're using:

HttpContext.Current.Response.Clear();

By clearing the response, you're losing the JavaScript that you added previously. It will never get to the client.

If the processing is fairly trivial (always just a couple of seconds), I'd just set the loading animation to run for a couple of seconds and stop, by setting a timeout on the initial onclick event. It's not perfect, but it'll give the user some immediate feedback.

If the processing is going to take a long or very variable amount of time, then the animation is more important to get right. You can try loading your Excel file in a hidden <iframe>, and attaching an onload event to remove the loading animation.

You would need to create a separate page to handle generating the Excel file, rather than doing it in a server-side OnClick handler. However, I seem to remember that support for onload events on <iframe> can be spotty with older IE versions.

javascript run in the client when page is loading in the browser. You may have a hidden textbox, at the end of you event yo can put a value into that textbox:

txtHidden.Text = "Hola Mundo"

You have to check the value on page load:

<script type="text/javascript">
$(document).ready(function(){
  if($("#txtHidden").length > 0 && $("#txtHidden").val() != '')
  {
    alert($("#txtHidden").val());
  }
});
</script>

You can put this in a web user control. Another solution:

<div class='button' id='btnGenerateDownload' onClick='GenerateDownload(this)'>
 Click here <div id='loadingImage' class='loadingImage'></div>
</div>

JQuery:

function GenerateDownload(caller)
{
   //add loading gif:
   var $loagingGIF = $(caller).children('#loadingImage').eq(0);
   $loagingGIF.addClass('loadingImage');
   var fileGeneratorUrl = 'ghFileGenerator.ashx';
   var downloadHandlerUrl = 'ghDownloadHandler.ashx';
   $.post({data: "File1"}, function(response){
     //remove gif
     $loagingGIF.removeClass('loadingImage');
     if(response != '') //file key
     {
       downloadHandlerUrl += '?key=' + response;
       var $link = $("<a />").attr('href', downloadHandlerUrl).html('download');
       $link.appendTo($(caller));
     }
   });
}

css:

.loadingImage{background: transparent url(images/loading.gif);}

.ashx:

 string filekey = context.Current.Request.Form("key");
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top