6

I need to run several methods after sending file to a user for a download. What happens is that after I send a file to a user, response is aborted and I can no longer do anything after response.end().

for example, this is my sample code:

 Response.Clear();
 Response.AddHeader("content-disposition", "attachment;  filename=test.pdf");
 Response.ContentType = "application/pdf";
 byte[] a = System.Text.Encoding.UTF8.GetBytes("test");
 Response.BinaryWrite(a);
 Response.End();
 StartNextMethod();
 Response.Redirect(URL);

So, in this example StartNextMethod and Response.Redirect are not executing.

What I tried is I created a separate handler(ashx) with the following code:

public void ProcessRequest(HttpContext context)
        {
            context.Response.Clear();
            context.Response.AddHeader("content-disposition", "attachment;  filename=test.pdf");
            context.Response.ContentType = "application/pdf";
            byte[] a = System.Text.Encoding.UTF8.GetBytes("test");
            context.Response.BinaryWrite(a);
            context.Response.End();
        }

and call it like this:

Download d = new Download();
d.ProcessRequest(HttpContext.Current);
StartNextMethod();
Response.Redirect(URL);

but the same error happen. I've tryied to replace Response.End with CompleteRequest but it doesn't help.

I guess the problem is that I'm using HttpContext.Current but should use a separate response stream. Is that correct? how do I do that in a separate method generically (Assume that I want my handler to accept byte array of data and content type and be downloadable from a separate response. I really do not want to use a separate page for a response.

UPDATE
I still didn't find a good solution. I'd like to do some actions after user has downloaded a file, but without using a separate page for a response\request thing.

Seraphim's
  • 12,559
  • 20
  • 88
  • 129
user194076
  • 8,787
  • 23
  • 94
  • 154

8 Answers8

7

Update
Since you said no second page, do this instead. Add a section to your page that checks for a query string parameter (something like fileid, or path, etc...). If this value is present then it initiates the download process using your existing code. If this value is not present then it runs like normal.

Now when the user clicks the download link you perform a post back (which you are already doing). In this post back create an iFrame on the page and set the URL of the iFrame to your pages URL with the added query string parameter (mypage.aspx?id=12664 or ?download=true, something like that). After creating the iframe perform what ever additional databinds/etc... you wish too.

Example
- http://encosia.com/ajax-file-downloads-and-iframes/

This above linked example uses an iFrame and an update panel, just like you are talking about.

Original Post
Response.Flush will allow you to continue processing after you send the file to the user, or just don't call Response.End (you don't really need too).

However Daniel A. White is correct, you can't actually redirect from your code after you send a file, you will get an error if you try. BUT you can continue to perform other server side operations if you need to.

Other answers agree with the general consensus, you can't redirect after a file starts downloading: https://stackoverflow.com/a/822732/328968 (PHP, but same concepts since it involves HTTP in general). or Directing to a new page after downloading a file.

Community
  • 1
  • 1
Peter
  • 9,643
  • 6
  • 61
  • 108
  • If I don't call response.end the generated file is corrupted. maybe there,s another issue, but still: if I do something like response.Flush(); button.Visible=false; it doesn't hide button. Or grid.DataBind() also doesn't work. – user194076 Jun 01 '12 at 18:04
  • @user194076 - I've updated my answer to include a single page option that may work for you. Though to be honest it's basically a way of faking the extra page option... – Peter Jun 01 '12 at 18:22
  • I haven't tried to implement this yet, but a simple example on how to achieve behavior with iframe and update panel would be helpful. – user194076 Jun 06 '12 at 16:22
  • @user194076 - I added a link to what looks like a great example. It even uses an updatepanel and iframe, just like you asked. – Peter Jun 06 '12 at 17:04
  • Awesome, thanks. I'll try to play with it in the next few hours and postback if have more questions. – user194076 Jun 06 '12 at 17:10
  • So, after some manipulation now I can add iframe on a page, but it doesn't fire a page load event like it is said in the tutorial. – user194076 Jun 07 '12 at 16:57
  • @user194076 - You are adding an iframe to the page and setting it's URL? If you are then whatever URL you set it to will load, this will not necessarily fire a Page_Load event for the page you are on, unless that is the page you are using in your iframe. – Peter Jun 07 '12 at 17:04
5

Response.End() throws a thread abort exception. It is designed to end your response. No code after that will process in that thread.

The End method causes the Web server to stop processing the script and return the current result. The remaining contents of the file are not processed.

What is it that you are trying to achieve?

If your purpose it to allow the pdf to download and then take the user to some other page, a little javascript can help you out.

Add a script with a timer to set location.href to your redirected paged.

nunespascal
  • 17,584
  • 2
  • 43
  • 46
  • Thanks for the reply. I know that response.end() throws an exception. I cannot use timer for redirecting to the other page since I do not know the length of processing. Also, in other place I need to run a database query after user downloaded a file. I cannot do that with javascript. – user194076 May 28 '12 at 05:35
  • if your pdf is generated by an ashx, you can do your database processing before you complete the response there. – nunespascal May 28 '12 at 05:44
  • The timer script could be added in the response you send after generate the pdf. This script itself would set the location.pref to your ashx path, so that the pdf is download. – nunespascal May 28 '12 at 05:46
1

As the previous answers had stated - returning PDF file means to send HTTP headers. You cannot send another headers after that, and Response.Redirect() simply means to send HTTP 302.

If you don't want to have separate page, or if you don't want to use AJAX, why not trying:

<head>
    <meta http-equiv="refresh" content="3; url=http://www.site.com/download.aspx?xxxx">
</head>

Actually this will show the desired page you want to show to the user, and will refresh the page after 3 sec with the URL for download of the PDF file.

Tisho
  • 8,320
  • 6
  • 44
  • 52
1

Download the file in chunks, as illustrated File Download in ASP.NET and Tracking the Status of Success/Failure of Download or in the answer to this question. When the last chunk of the file has been written to the client you can execute the code you need to. (Doesn't have to be at the end, can be anywhere in between depending upon your needs.)

Community
  • 1
  • 1
jzonthemtn
  • 3,344
  • 1
  • 21
  • 30
0

the user clicks on a download button on WebForm1.aspx to start downloading a file. then, after the file download is done (served by WebForm2.aspx), user is automatically redirected.

WebForm1.aspx

<script type="text/javascript">
    $(document).ready(function () {
        $('#btnDL').click(function () {
            $('body').append('<iframe src="WebForm2.aspx" style="display:none;"></iframe>');
            return true;
        });
    });
</script>
<asp:Button runat="server" ID="btnDL" ClientIDMode="Static" Text="Download" OnClick="btnDL_Click" />

WebForm1.aspx.cs

protected void btnDL_Click(object sender, EventArgs e)
{
    var sent = Session["sent"];

    while (Session["sent"]==null)
    {// not sure if this is a bad idea or what but my cpu is NOT going nuts
    }

    StartNextMethod();
    Response.Redirect(URL);

}

WebForm2.aspx.cs

protected void Page_Load(object sender, EventArgs e)
{
    Response.Clear();
    Response.AddHeader("content-disposition", "attachment;  filename=test.pdf");
    Response.ContentType = "application/pdf";
    byte[] a = System.Text.Encoding.UTF8.GetBytes("test");
    Response.BinaryWrite(a);
    Session["sent"] = true;
}

Global.asax.cs

protected void Session_Start(object sender, EventArgs e)
{
    Session["init"] = 0; // init and allocate session data storage
}

note: make sure don't use ashx (generic handler) to serve your download. for some reason, the session in ashx and aspx don't talk to each other, unless you implement this.

Ray Cheng
  • 12,230
  • 14
  • 74
  • 137
0

Just remove the context.Response.End(); because you are redirecting anyway...

The problem is flawed logic here.... Why would you end the response?

Get the PDF and display a link to it or use a META refresh to redirect to the location of the PDF or you could also display a link or use a combination of both techniques.

Jay
  • 3,276
  • 1
  • 28
  • 38
0

I believe what you are trying won't work.

This is what I would do:

  1. Write content to a file locally and assign it an unique id
  2. send user to the next page that contains a hidden frame that perform a request with the unique id (javascript)
  3. hidden request page loads file and push on the content stream.

This is the same behavior a lot of file download sites is using. Only issue is if the hidden frame fails (javascript turned off) to perform the request, why a lot of the same sites have the link available if the auto request fails.

Disadvantage: file cleanup.

Andrea
  • 11,801
  • 17
  • 65
  • 72
Quintium
  • 499
  • 5
  • 22
0

I recommend this solution :

  1. Don't use response.End();

  2. Declare this global var : bool isFileDownLoad;

  3. Just after your (Response.BinaryWrite(a);) set ==> isFileDownLoad = true;

  4. Override your Render like :

    /// /// AEG : Very important to handle the thread aborted exception /// /// override protected void Render(HtmlTextWriter w) { if (!isFileDownLoad) base.Render(w); }

Abdelrahman ELGAMAL
  • 414
  • 2
  • 7
  • 15