Downloading Data as a File from a MemoryStream Using a HTTP Handler

Description

This article will show you how to buffer data into a MemoryStream and output the buffered data back to the browser as a text file using a HTTP Handler.

Introduction

In a previous article I showed an example in which a file was created from a memory stream and sent back to the browser as a text file via an ASP.NET page. Though this methodology worked, it left an orphaned ASP.NET page open after the download was complete. One way to overcome this is to use a HTTP Handler to execute the code that generates the text file and sends it back to the browser. 

The Code:

The first step in this example is to create the HTTP Handler. This handler implements three methods, two of which are required by the IhttpHandler interface and one which is used to retirieve data from the database and return it as a memory stream.

using System;
using System.Web;
using System.IO;
using System.Data.SqlClient;
using System.Data;
namespace MemStreamDloadHTTPHandler

public class DownloadHandler : IHttpHandler
{
private MemoryStream GetData()
{
//Create the return memorystream object that will hold
//the buffered data.
MemoryStream ReturnStream = new MemoryStream();
//Put together the connection string
string cn = "Persist Security Info=False;Integrated Security=false;User ID=sa;Pwd=foo ;database=northwind;server=foo;Connect Timeout=30";
try
{
//Get the sql string
string SqlString = "select * from dbo.Orders";
//Create a new command object
SqlCommand sc = new SqlCommand();
//Set the connection
sc.Connection = new SqlConnection(cn);
//Open the connection
sc.Connection.Open();
//Set the command type
sc.CommandType = CommandType.Text;
//Set the SQL String
sc.CommandText = SqlString;
//Execute the SQL Statement/Procedure
SqlDataReader RS = sc.ExecuteReader();
//Create a streamwriter to write to the memory stream
StreamWriter sw = new StreamWriter(ReturnStream);
if (RS.Read())
{
//Create a header for the file from the names of the
//columns that we are retrieving
string tempstring = "";
for (int counter = 0; counter < RS.FieldCount;counter++)
{
tempstring += RS.IsDBNull(counter)?"":RS.GetName(counter) + "\t";
}
//Write the row of data to the Memory Stream.
sw.WriteLine(tempstring); 
//Loop through the data row and write the contents to the memory
//stream.
while (RS.Read())
{
//Put each columns' data value into a string separated by a tab.
tempstring = "";
for (int counter = 0; counter < RS.FieldCount;counter++)
{
tempstring += RS.IsDBNull(counter)?"":RS.GetValue(counter).ToString() + "\t";
}
//Write the row of data to the Memory Stream.
sw.WriteLine(tempstring);
}
}
//Clean up the stream writer
sw.Flush();
sw.Close();
//Clean up the data reader
RS.Close();
//Clean up the command object
sc.Connection.Close();
sc.Dispose();
}
catch (Exception Ex)
{
throw Ex;

//Return the memory Stream
return ReturnStream;
}
// Override the ProcessRequest method.
public void ProcessRequest(HttpContext context)
{
try
{
//Create and populate a memorystream with the contents of the
//database table
System.IO.MemoryStream mstream = GetData();
//Convert the memorystream to an array of bytes.
byte[] byteArray = mstream.ToArray();
//Clean up the memory stream
mstream.Flush();
mstream.Close();
// Clear all content output from the buffer stream
context.Response.Clear();
// Add a HTTP header to the output stream that specifies the default filename
// for the browser's download dialog
context.Response.AddHeader("Content-Disposition", "attachment; filename="+context.Request.Form["txtFileName"].ToString());
// Add a HTTP header to the output stream that contains the
// content length(File Size). This lets the browser know how much data is being transfered
context.Response.AddHeader("Content-Length", byteArray.Length.ToString());
// Set the HTTP MIME type of the output stream
context.Response.ContentType = "application/octet-stream";
// Write the data out to the client.
context.Response.BinaryWrite(byteArray);

catch (Exception Ex)
{
throw Ex;

}
// Override the IsReusable property.
public bool IsReusable
{
get { return false; }
}
}
}

Now that the HTTP handler has been created we need to configure it for our project. The following Web.Config section shows how to configure the application to use the handler.

<httpHandlers>
<
add verb="*" path="Download.aspx" type="MemStreamDloadHTTPHandler.DownloadHandler,MemStreamDloadHTTPHandler"/>
</
httpHandlers>

The last thing we need to do is to create a simple HTML Form that will post will to the Handler (Download.aspx). 

Summary

As you can see, this solution is pretty straight forward. Through the use of a HTTP Handler you can download a file from a memory stream without creating an orphaned ASP.Net page.