Create an HTTP handler to read the required file from the filesystem and send it to the browser. The steps for creating an HTTP handler are defined in Recipe 17.1.
Example 17-8 and Example 17-9 show the VB and C# class files
we’ve written to implement a file download HTTP
handler. Example 17-10 through Example 17-12 show the .aspx
file and
the VB and C# code-behind files for our application that demonstrates
the use of the file download HTTP handler.
Of the many ways you might implement a reusable file download routine that can handle virtually any file type, creating an HTTP handler makes the most sense. HTTP handlers are designed specifically to process requests for resources, and a file download request is merely a special instance of such a request.
The file download HTTP handler described in our example downloads a file from the local filesystem to the user’s system. The name of the file to download is passed in the URL used to access the HTTP handler.
The first step in implementing the file download HTTP handler is to
create a class that implements IHttpHandler
. The
class can be part of your web project, or, if you want it to be
reusable across applications, place it in a project by itself so a
separate assembly can be created, as described in Recipe 17.1.
As discussed in the previous recipe, implementing the
IHttpHandler
interface requires the implementation
of two methods: IsReusable
and
ProcessRequest
. IsReusable
is a
property that explicitly returns a Boolean value that indicates
whether the HTTP handler can be reused by other HTTP requests. For
synchronous handlers like our example, the property should always
return false so the handler is not pooled (kept in memory).
The ProcessRequest
method provides all of the
functionality required to download the file. The first step is to
retrieve the name of the file that is to be downloaded from the URL
that is being processed by the handler.
In our example, we provide only the filename in the URL that calls
the handler, because all files to be downloaded are intended to be
located in a single Downloads
directory
associated with the application. However, because a fully qualified
filename is required for the download along with the size of the
file, a FileInfo
object is created passing the
fully qualified name of the file to the constructor. The path to the
Downloads
directory is obtained using
Server.MapPath
, which translates a relative path
within the web application to a fully qualified path on the web
server’s filesystem.
It is important to carefully review how you’ve
implemented your file download handler to make sure it is not
possible for a hacker to download files you do not intend to have
downloaded. For example, if you do not restrict downloadable files to
a single area, like we have done in our example, a hacker could
possibly enter the following to download your
web.config
file:
http://aspnetcookbook/FileDownloadHandlerVB.aspx?filename=web.config
By restricting downloadable files to a single area and providing the path information in your code instead of in the URL, you can block a hacker from accessing restricted folders.
To send a file to the browser, you must write it to the
Response
object. Passing data in the response
object is the only way to return data to the browser. Based on the
content type (described later), the browser processes the data
returned using the Response
object.
The first step in writing the data to the Response
object is to clear any data currently in the object, because no other
data can be included with the file or a corrupted file error will
occur.
The AddHeader
method of the
Response
object is then used to add the name of
the file being downloaded and its length.
The content type then needs to be set. In our example it is set to
"application/octet-stream
" so it will be treated
by the browser as a binary stream and prompt the user to select the
location to save the file. For your application, you may want to set
the content type to the explicit file type, such as
"application/PDF
" or
"application/msword
“. Setting the content type to
the explicit file type allows the browser to open it with the
application defined to handle the specified file type on the client
machine. For more information on content types, consult http://www.w3c.org.
The file is then written to the Response
object
and the response is ended to send the file to the browser.
The web.config
file for our application must
have an entry in the <httpHandlers>
element
to tell ASP.NET which URL requests need to routed to the file
download HTTP handler. You use the add
element and
its attributes to specify each custom handler. The
verb
attribute of the add
element defines the types of requests that are routed to the HTTP
handler. The allowable values are *
,
GET
, HEAD
, and
POST
. The value *
is a wildcard
for all request types.
The path
attribute defines the URL(s) that are to
be processed by the HTTP handler. The path can be a single URL, as
shown later, or it can be set to something like
"*.download
" to have the HTTP handler process all
requests for URLs with a download
extension. (See
the note in Recipe 17.1 regarding
request extensions.)
The type
attribute defines the name of the
assembly and class within the assembly that will process the request
in the format type=
"class name
,
assembly
“.
The class must be identified by its full namespace. The following
code shows how to add a reference to the file download handler to the
web.config
file of our sample application:
<configuration> <system.web> <httpHandlers><add verb="*" path="FileDownloadHandlerVB.aspx"
type="ASPNetCookbook.VBExamples.HttpHandlers.FileDownloadHandlerVB,
VBFileDownloadHandler"/>
</httpHandlers> </system.web> </configuration> <configuration> <system.web> <httpHandlers><add verb="*" path="FileDownloadHandlerCS.aspx"
type="ASPNetCookbook.CSExamples.HttpHandlers.FileDownloadHandlerCS,
CSFileDownloadHandler"/>
</httpHandlers> </system.web> </configuration>
To use the HTTP handler described earlier to download files, we need
to set the href
attribute of an HTML anchor tag to
the name of the file download HTTP handler defined in the path
attribute of the entry added to web.config
,
passing the name of the file to download in the URL. In our example,
the href
attribute of the anchor tag is set in the
Page_Load
method of the test page. A sample of the
URL is shown here:
href="FileDownloadHandlerVB.aspx?Filename=SampleDownload.txt"
This example demonstrates downloading a file that already exists on the web server. The code can easily be altered to pass additional data in the URL, then programmatically generate the file and send it to the browser without saving it to the filesystem. This could be useful if you are dynamically creating a PDF or a CSV file requested by the user.
Recipe 17.1 for techniques on how to create a generic, reusable HTTP handler; http://www.w3c.org for information on content types
Example 17-8. File download HTTP handler (.vb)
Option Explicit On Option Strict On '----------------------------------------------------------------------------- ' ' Module Name: FileDownloadHandlerVB.vb ' ' Description: This class provides a file download handler as an HTTP ' handler. ' '***************************************************************************** Imports System.IO Imports System.Web Namespace ASPNetCookbook.VBExamples.HttpHandlers Public Class FileDownloadHandlerVB Implements IHttpHandler'The following constant is used in the URL used to access this handler to
'define the file to download
Public Const QS_FILENAME As String = "Filename"
'the following constant defines the folder containing downloadable files
Private Const DOWNLOAD_FOLDER As String = "Downloads"
'*************************************************************************** ' ' ROUTINE: IsReusable ' ' DESCRIPTION: This property defines whether another HTTP handler can ' reuse this instance of the handler. ' ' NOTE: False is always returned since this handler is synchronous ' and is not pooled. '--------------------------------------------------------------------------- Public ReadOnly Property IsReusable( ) As Boolean _ Implements IHttpHandler.IsReusable Get Return (False) End Get End Property 'IsReusable '*************************************************************************** ' ' ROUTINE: ProcessRequest ' ' DESCRIPTION: This routine provides the processing for the http request. ' It is responsible for reading the file from the local ' file system and writing it to the response object. '---------------------------------------------------------------------------Public Sub ProcessRequest(ByVal context As HttpContext) _
Implements IHttpHandler.ProcessRequest
Dim file As FileInfo
Dim filename As String
'get the filename from the querystring
filename = context.Request.QueryString(QS_FILENAME)
'get the file data since the length is required for the download
file = New FileInfo(context.Server.MapPath(DOWNLOAD_FOLDER) & "" & _
filename)
'write it to the browser
context.Response.Clear( )
context.Response.AddHeader("Content-Disposition", _
"attachment; filename=" & filename)
context.Response.AddHeader("Content-Length", _
file.Length.ToString( ))
context.Response.ContentType = "application/octet-stream"
context.Response.WriteFile(file.FullName)
context.Response.End( )
End Sub 'ProcessRequest
End Class 'FileDownloadHandlerVB End Namespace
Example 17-9. File download HTTP handler (.cs)
//---------------------------------------------------------------------------- // // Module Name: FileDownloadHandlerCS // // Description: This class provides a file download handler as an HTTP // handler. // //**************************************************************************** using System; using System.IO; using System.Web; namespace ASPNetCookbook.CSExamples.HttpHandlers { public class FileDownloadHandlerCS : IHttpHandler {// The following constant is used in the URL used to access this handler
// to define the file to download
public const string QS_FILENAME = "Filename";
//the following constant defines the folder containing downloadable files
private const string DOWNLOAD_FOLDER = "Downloads";
//************************************************************************ // // ROUTINE: IsReusable // // DESCRIPTION: This property defines whether another HTTP handler can // reuse this instance of the handler. // // NOTE: false is always returned since this handler is synchronous // and is not pooled. //------------------------------------------------------------------------ public bool IsReusable { get { return(false); } } // IsReusable //************************************************************************ // // ROUTINE: ProcessRequest // // DESCRIPTION: This routine provides the processing for the http // request. It is responsible for reading image data from // the database and writing it to the response object. //------------------------------------------------------------------------public void ProcessRequest(HttpContext context)
{
FileInfo file = null;
string filename = null;
// get the filename from the querystring
filename = context.Request.QueryString[QS_FILENAME];
// get the file data since the length is required for the download
file = new FileInfo(context.Server.MapPath(DOWNLOAD_FOLDER) + "\" +
filename);
// write it to the browser
context.Response.Clear( );
context.Response.AddHeader("Content-Disposition",
"attachment; filename=" + filename);
context.Response.AddHeader("Content-Length",
file.Length.ToString( ));
context.Response.ContentType = "application/octet-stream";
context.Response.WriteFile(file.FullName);
context.Response.End( );
} // ProcessRequest
} // FileDownloadHandlerCS }
Example 17-10. Using the file download HTTP handler (.aspx)
<%@ Page Language="vb" AutoEventWireup="false"
Codebehind="CH17TestHTTPFileDownloadHandlerVB.aspx.vb"
Inherits="ASPNetCookbook.VBExamples.CH17TestHTTPFileDownloadHandlerVB" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Test HTTP File Download Handler</title>
<link rel="stylesheet" href="css/ASPNetCookbook.css">
</head>
<body leftmargin="0" marginheight="0" marginwidth="0" topmargin="0">
<form id="frmTestFileDownloadHandler" method="post" runat="server">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center">
<img src="images/ASPNETCookbookHeading_blue.gif">
</td>
</tr>
<tr>
<td class="dividerLine">
<img src="images/spacer.gif" height="6" border="0"></td>
</tr>
</table>
<table width="90%" align="center" border="0">
<tr>
<td align="center"> </td>
</tr>
<tr>
<td align="center" class="PageHeading">
HTTP Handler For File Downloads (VB)
</td>
</tr>
<tr>
<td><img src="images/spacer.gif" height="10" border="0"></td>
</tr>
<tr>
<td align="center" class="SubHeading">
Click the link below to download the sample file using
an HTTP Handler<br />
<br />
<a id="anDownload" runat="server" ></a>
</td>
</tr>
</table>
</form>
</body>
</html>
Example 17-11. Using the file download HTTP handler code-behind (.vb)
Option Explicit On Option Strict On '----------------------------------------------------------------------------- ' ' Module Name: CH17TestHTTPFileDownloadHandlerVB.aspx.vb ' ' Description: This module CH17TestHTTPFileDownloadHandlerVB the code-behind ' for the CH17TestHTTPFileDownloadHandlerVB.aspx page. ' '***************************************************************************** Imports ASPNetCookbook.VBExamples.HttpHandlers Namespace ASPNetCookbook.VBExamples Public Class CH17TestHTTPFileDownloadHandlerVB Inherits System.Web.UI.Page 'controls on the form Protected anDownload As System.Web.UI.HtmlControls.HtmlAnchor '*************************************************************************** ' ' ROUTINE: Page_Load ' ' DESCRIPTION: This routine provides the event handler for the page load ' event. It is responsible for initializing the controls ' on the page. '--------------------------------------------------------------------------- Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.LoadConst FILE_TO_DOWNLOAD As String = "SampleDownload.txt"
'set the text and href of the download anchor
anDownload.InnerText = FILE_TO_DOWNLOAD
anDownload.HRef = "FileDownloadHandlerVB.aspx?" & _
FileDownloadHandlerVB.QS_FILENAME & "=" & _
FILE_TO_DOWNLOAD
End Sub 'Page_Load End Class 'HTTPFileDownloadHandlerDemo_VB End Namespace
Example 17-12. Using the file download HTTP handler code-behind (.cs)
//---------------------------------------------------------------------------- // // Module Name: CH17TestHTTPFileDownloadHandlerCS.aspx.cs // // Description: This module provides the code behind for the // CH17TestHTTPFileDownloadHandlerCS.aspx page // //**************************************************************************** using ASPNetCookbook.CSExamples.HttpHandlers; using System; namespace ASPNetCookbook.CSExamples { public class CH17TestHTTPFileDownloadHandlerCS : System.Web.UI.Page { // controls on the form protected System.Web.UI.HtmlControls.HtmlAnchor anDownload; //************************************************************************ // // ROUTINE: Page_Load // // DESCRIPTION: This routine provides the event handler for the page // load event. It is responsible for initializing the // controls on the page. //------------------------------------------------------------------------ private void Page_Load(object sender, System.EventArgs e) {const string FILE_TO_DOWNLOAD = "SampleDownload.txt";
// set the text and href of the download anchor
anDownload.InnerText = FILE_TO_DOWNLOAD;
anDownload.HRef = "FileDownloadHandlerCS.aspx?" +
FileDownloadHandlerCS.QS_FILENAME + "=" +
FILE_TO_DOWNLOAD;
} // Page_Load } // CH17TestHTTPFileDownloadHandlerCS }