Ok, have you ever tried downloading a document using a server-side Click event on a button? You probably did something like sending the content in the response as a stream, like this?:
private void DownloadDocument(int documentId)
{
string filePath = Server.MapPath(String.Concat("Document_", documentId, ".pdf"));
System.IO.FileInfo file = new System.IO.FileInfo(filePath);
if(file.Exists)
{
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=" + file.Name);
Response.AddHeader("Content-Length", file.Length.ToString());
Response.ContentType = "application/octet-stream";
Response.WriteFile(file.FullName);
Response.End();
}
else
{
Response.Write("This file does not exist.");
this.DocumentFiledCheckBox.Enabled = false;
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "AsyncCallbacks", "");
}
}
Now what if you wanted to set the values of certain controls on the form, in addition to sending the pdf stream? (e.g. you wanted to enable a check box, so the user can indicate whether she has filed the document, but this check box should only be available if she has downloaded it)
Maybe you had something like this:
protected void DownloadButton_Click(object sender, EventArgs e)
{
//Enable the check box, as per requirement
this.DocumentFiledCheckBox.Enabled = true;
DownloadDocument(101);
}
Problem is that you won't be able to set the control values in this way, probably because you are streaming an entirely different response.
Scratch your head for a day and a half on this, and then try the following solution:
MY SOLUTION
In the aspx file
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="Default" ValidateRequest="false" EnableEventValidation="false" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Download a Document</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="DocumentIdTextBox" Text = "101" runat="server" />
<asp:Button ID="DownloadButton" runat="server" OnClick="DownloadButton_Click" Text="Download Document" />
<br />
<br />
<asp:CheckBox ID="DocumentFiledCheckBox" Text="Document Filed" Enabled="false" runat="server" />
<asp:Button ID="CallbackButton" runat="server" OnClick="CallbackButton_Click" Text="DownloadDocumentCallbackPlaceholderButton" UseSubmitBehavior="False" Visible="True" /></div>
</form>
</body>
</html>
We have a simple form containing:
- a DocumentIdTextBox Text Box control for the document ID you want to download.
-a hidden CallbackButton Text Box controls which you are going to use to do an automated post back when the response has been downloaded from the server.
- a DocumentFiledCheckBox which will be enabled when you click the download button, otherwise its initial state is disabled.
- Notice that the UseSubmitBehavior property for each of the buttons above is set to false. This will enable ASP.NET to render the __doPostBack functions. If you do not set this property, you may not get a __doPostBack function in your browser. This is presumably because a submit button will post to the server as part of the behaviour provided by HTML. See point 5 of the .cs file notes below, for more on this...
In the .cs file:
1. Handle the initial click event:
//1. Download button is clicked
protected void DownloadButton_Click(object sender, EventArgs e)
{
//Enable the check box, as per requirement
this.DocumentFiledCheckBox.Enabled = true;
//Cause the browser to download the document
SetUpClientSideScriptToDownloadDocument(int.Parse(this.DocumentIdTextBox.Text));
}
Notice that the key bit here is to send a client-side callback which fires as soon as the response is received
2. Fire a client-side callback:
Notice that we need to set a timeout, and attach to the onload event of the window. Otherwise, you may find that your code breaks every so often, in search of your mystery client-side control, which has not loaded yet.
You are welcome to try raising the click event of our hidden control by using __doPostBack... This didn't work for me. I could not get the server to fire the Click event. Instead, it just went wizzing by my Click event handler. It did do a postback, though.
// 2. Browser receives this code --> we use __doPostBack as an alternative to window.open()
//to avoid Popup blockers, used by most browsers these days.
private void SetUpClientSideScriptToDownloadDocument(int documentId)
{
//Defer tag should make this fire only after the full response has been received
//This didn't work for me
// string javaScript = @"<script language='JavaScript' defer='defer'>
// __doPostBack('CallbackButton','onclick');
// </script>";
//Set a timeout so that this fires timeously i.e. AFTER entire response has been received
string javaScript = @"<script language='javascript' type='text/javascript'>
window.attachEvent('onload',function(){setTimeout('DownloadKeyFactsDocument()',50);});
function DownloadKeyFactsDocument()
{
//__doPostBack('CallbackButton','onclick');
document.getElementById('" + this.CallbackButton.ClientID + @"').click();
}
</script>";
if (!Page.ClientScript.IsClientScriptBlockRegistered("AsyncCallbacks"))
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "AsyncCallbacks", javaScript);
}
3. Handle the Callback on the server:
Our server-side function handles the click event of our invisible button.
//3. Browser calls this function(browser calls __doPostBack('CallbackButton', 'onclick');
protected void CallbackButton_Click(object sender, EventArgs e)
{
this.DownloadDocument(int.Parse(this.DocumentIdTextBox.Text));
}
4. Download the document
The rest is history...
//4. Stream the document to the browser from the server
private void DownloadDocument(int documentId)
{
string filePath = Server.MapPath(String.Concat("Document_", documentId, ".pdf"));
System.IO.FileInfo file = new System.IO.FileInfo(filePath);
if(file.Exists)
{
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=" + file.Name);
Response.AddHeader("Content-Length", file.Length.ToString());
Response.ContentType = "application/octet-stream";
Response.WriteFile(file.FullName);
Response.End();
}
else
{
Response.Write("This file does not exist.");
this.DocumentFiledCheckBox.Enabled = false;
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "AsyncCallbacks", "");
}
}
}
5. Make sure you get a __doPostBack function as part of the response
The following code in Page_Load ensures that the browser gets a __doPostBack function:
protected void Page_Load(object sender, EventArgs e)
{
//Must always render __doPostBack
// (even when callback button is invisible, and/or when 'UseSubmitBehavior' is true)
//Notice that these two conditions (invisible and UseSubmitBehavior=true) will cause
//ASP.Net not to render the __doPostBack function
Page.ClientScript.GetPostBackClientHyperlink(this, "");
this.CallbackButton.Attributes.Add("style", "display:none");
}
6. Make sure you hide your control correctly
Just in case you don't want to see the dummy control, you will also use this code in Page Load:
this.CallbackButton.Attributes.Add("style", "display:none");
Why don't we just use server side code, and set the Visible property of the button to false? The answer is that if you do this, ASP>NET won't render the control. So trying this will make our infrastructure will fall over!
Here is a complete code listing for you:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="Default" ValidateRequest="false" EnableEventValidation="false" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Download a Document</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="DocumentIdTextBox" Text = "101" runat="server" />
<asp:Button ID="DownloadButton" runat="server" OnClick="DownloadButton_Click" Text="Download Document" />
<br />
<br />
<asp:CheckBox ID="DocumentFiledCheckBox" Text="Document Filed" Enabled="false" runat="server" />
<asp:Button ID="CallbackButton" runat="server" OnClick="CallbackButton_Click" Text="DownloadDocumentCallbackPlaceholderButton" UseSubmitBehavior="False" Visible="True" /></div>
</form>
</body>
</html>
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//Must always render __doPostBack
// (even when callback button is invisible, and/or when 'UseSubmitBehavior' is true)
//Notice that these two conditions (invisible and UseSubmitBehavior=true) will cause
//ASP.Net not to render the __doPostBack function
Page.ClientScript.GetPostBackClientHyperlink(this, "");
this.CallbackButton.Attributes.Add("style", "display:none");
}
//1. Download button is clicked
protected void DownloadButton_Click(object sender, EventArgs e)
{
//Enable the check box, as per requirement
this.DocumentFiledCheckBox.Enabled = true;
//Cause the browser to download the document
SetUpClientSideScriptToDownloadDocument(int.Parse(this.DocumentIdTextBox.Text));
}
//2. Browser receives this code --> we use __doPostBack as an alternative to window.open()
//to avoid Popup blockers, used by most browsers these days.
private void SetUpClientSideScriptToDownloadDocument(int documentId)
{
//Defer tag should make this fire only after the full response has been received
//This didn't work for me
// string javaScript = @"<script language='JavaScript' defer='defer'>
// __doPostBack('CallbackButton','onclick');
// </script>";
//Set a timeout so that this fires timeously i.e. AFTER entire response has been received
string javaScript = @"<script language='javascript' type='text/javascript'>
window.attachEvent('onload',function(){setTimeout('DownloadKeyFactsDocument()',50);});
function DownloadKeyFactsDocument()
{
//__doPostBack('CallbackButton','onclick');
document.getElementById('" + this.CallbackButton.ClientID + @"').click();
}
</script>";
if (!Page.ClientScript.IsClientScriptBlockRegistered("AsyncCallbacks"))
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "AsyncCallbacks", javaScript);
}
//3. Browser calls this function(browser calls __doPostBack('CallbackButton', 'onclick');
protected void CallbackButton_Click(object sender, EventArgs e)
{
this.DownloadDocument(int.Parse(this.DocumentIdTextBox.Text));
}
//4. Stream the document to the browser from the server
private void DownloadDocument(int documentId)
{
string filePath = Server.MapPath(String.Concat("Document_", documentId, ".pdf"));
System.IO.FileInfo file = new System.IO.FileInfo(filePath);
if(file.Exists)
{
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=" + file.Name);
Response.AddHeader("Content-Length", file.Length.ToString());
Response.ContentType = "application/octet-stream";
Response.WriteFile(file.FullName);
Response.End();
}
else
{
Response.Write("This file does not exist.");
this.DocumentFiledCheckBox.Enabled = false;
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "AsyncCallbacks", "");
}
}
}