Here's is some sample code for getting asp.net applications to prompt for download an attachment file.
A good source for mapping file extensions to mime-types can be found at
MIME Types.
It is important to specify both the mime-type in the asp.net response context and add a "Content-Disposition" header so that the browser will treat the data stream as an attachment.
default.aspx
The aspx page should only contain the page declaration. It should not contain any html. For example:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="SampleWebAttachment._Default" %>
The code behind file is something like this:
default.aspx.cs
This is the bare minimum necessary to cause the browser to prompt with the "File Download" Dialog.
You must specify:
- The ContentType (The mime-type of the content returned via the HTTP response)
- Add the "Content-Disposition" HTTP header so the browser knows that the content is to be treated as an attachment.
- Use WriteFile to directly write the files stream into the HTTP response.

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Context.Response.ContentType = "application/msword";
Context.Response.AddHeader("Content-disposition", "attachment; filename=Something.docx");
Context.Response.WriteFile(@"C:\Users\adaisleyharrison\Documents\Visual Studio 2008\Projects\SampleWebAttachment\SampleWebAttachment\files\Something.docx");
Context.Response.End();
}
}
Downloading binary data streams
Generalizing this to download such any stream data data look something like this:
public partial class DownloadAStream : System.Web.UI.Page
{
public const int DEFAULT_BUFFER_SIZE = 4096;
protected void Page_Load(object sender, EventArgs e)
{
Context.Response.ContentType = "application/msword";
Context.Response.AddHeader("Content-disposition", "attachment; filename=Something.docx");
FileStream stream = File.Open(@"C:\Users\adaisleyharrison\Documents\Visual Studio 2008\Projects\SampleWebAttachment\SampleWebAttachment\files\Something.docx", FileMode.Open);
writeStream(stream);
Context.Response.End();
}
private void writeStream(Stream stream)
{
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
try
{
int bytesRead;
while ( (bytesRead = stream.Read( buffer, 0, buffer.Length )) > 0 )
{
Context.Response.OutputStream.Write(buffer, 0, bytesRead);
}
}
finally
{
stream.Close();
}
}
}
The writeStream function is really nothing but a data pump. It reads from one stream and writing to the HTTP OutputStream.
Mime-Type Manager Class
If you are mapping several different file types you may want to create a lookup class to help with the mapping of file extension to mime-type.
Here is a simple manager class that can be used to map between file extensions and mime-types:
public class MimeTypeManager
{
private Dictionary<string, string> _mimeTypeByExtension;
public const string DEFAULT_MIME_TYPE = "application/octet-stream";
public string DefaultMimeType { get; set; }
public MimeTypeManager()
{
this.DefaultMimeType = DEFAULT_MIME_TYPE;
_mimeTypeByExtension = new Dictionary<string,string>();
}
public void RegisterMimeType( string extension, string mimeType )
{
mimeType = mimeType.ToLowerInvariant();
_mimeTypeByExtension.Add(extension, mimeType);
}
public string ToMimeType(string extension)
{
string mimeType;
extension = extension.ToLowerInvariant();
if (!_mimeTypeByExtension.TryGetValue(extension, out mimeType))
{
mimeType = this.DefaultMimeType;
}
return mimeType;
}
}
With this helper class we can further generize download page functionality as follows:
public partial class GenericDownload : System.Web.UI.Page
{
public const int DEFAULT_BUFFER_SIZE = 4096;
private static MimeTypeManager _mimeTypeManager;
static GenericDownload()
{
_mimeTypeManager = new MimeTypeManager();
_mimeTypeManager.RegisterMimeType(".doc", "application/msword");
_mimeTypeManager.RegisterMimeType(".docx", "application/msword");
/*... more definitions here ...*/
_mimeTypeManager.RegisterMimeType(".htm", "text/html");
_mimeTypeManager.RegisterMimeType(".html", "text/html");
}
protected void Page_Load(object sender, EventArgs e)
{
string filePath = @"C:\Users\adaisleyharrison\Documents\Visual Studio 2008\Projects\SampleWebAttachment\SampleWebAttachment\files\Something.docx";
try
{
FileStream stream = File.Open(filePath, FileMode.Open);
writeAttachment(stream, filePath);
}
catch (FileNotFoundException exception)
{
Context.Response.Write("<html><body><h1>File Not Found</h1></body></html>");
Context.Response.End();
}
}
private void writeAttachment(Stream stream, string filePath)
{
string filename = Path.GetFileName(filePath);
string extension = Path.GetExtension(filePath);
Context.Response.ContentType = _mimeTypeManager.ToMimeType(extension);
Context.Response.AddHeader("Content-disposition", "attachment; filename=" + filename);
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
try
{
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
Context.Response.OutputStream.Write(buffer, 0, bytesRead);
}
}
finally
{
stream.Close();
}
Context.Response.End();
}
}
Downloading SQL Blobs as Attachments
Here is a slight modification for reading blobs from SQL and downloading them as attachments:
public partial class downloadblog : System.Web.UI.Page
{
private SqlConnection _sqlConnection;
public const int DEFAULT_BUFFER_SIZE = 4096;
private static MimeTypeManager _mimeTypeManager;
static downloadblog()
{
_mimeTypeManager = new MimeTypeManager();
_mimeTypeManager.RegisterMimeType(".doc", "application/msword");
_mimeTypeManager.RegisterMimeType(".docx", "application/msword");
_mimeTypeManager.RegisterMimeType(".word", "application/msword");
_mimeTypeManager.RegisterMimeType(".w6w", "application/msword");
_mimeTypeManager.RegisterMimeType(".mdb", "application/msaccess");
_mimeTypeManager.RegisterMimeType(".xla", "application/msexcel");
_mimeTypeManager.RegisterMimeType(".xls", "application/msexcel");
/* ... more mime types ...*/
_mimeTypeManager.RegisterMimeType(".cpp", "text/plain");
_mimeTypeManager.RegisterMimeType(".vb", "text/plain");
_mimeTypeManager.RegisterMimeType(".aspx", "text/xml");
_mimeTypeManager.RegisterMimeType(".xml", "text/xml");
_mimeTypeManager.RegisterMimeType(".xslt", "text/xml");
_mimeTypeManager.RegisterMimeType(".xsl", "text/xml");
_mimeTypeManager.RegisterMimeType(".htm", "text/html");
_mimeTypeManager.RegisterMimeType(".html", "text/html");
}
protected void Page_Load(object sender, EventArgs e)
{
string blobId = Context.Request["blobId"];
string blobFilePath;
SqlCommand getBlobRecord = new SqlCommand(
"SELECT Blob, FilePath "+
"FROM BlobTable "+
"WHERE BlobIndex = @blobId ", _sqlConnection);
getBlobRecord.Parameters.Add("@blobId", SqlDbType.NVarChar, 20).Value = blobId;
// Open the connection and read data into the DataReader.
_sqlConnection.Open();
SqlDataReader blobTableReader = getBlobRecord.ExecuteReader(CommandBehavior.SequentialAccess);
try
{
if (blobTableReader.Read())
{
blobFilePath = blobTableReader.GetString(1);
writeBlobAsAttachment(blobTableReader, 0, blobFilePath);
}
}
finally
{
blobTableReader.Close();
}
}
public void writeBlobAsAttachment(DbDataReader dataReader, int blobColumnIndex, string filePath)
{
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
string filename = Path.GetFileName(filePath);
string extension = Path.GetExtension(filePath);
long startIndex = 0; //Start of the blob
long bytesRead;
Stream outputStream = Context.Response.OutputStream;
Context.Response.ContentType = _mimeTypeManager.ToMimeType(extension);
Context.Response.AddHeader("Content-disposition", "attachment; filename=" + filename);
// Continue reading and writing while there are bytes to be read.
while ((bytesRead = dataReader.GetBytes(blobColumnIndex, startIndex, buffer, 0, buffer.Length)) > 0)
{
outputStream.Write(buffer, 0, (int)bytesRead);
// Reposition the start index to the end of the last buffer and fill the buffer.
startIndex += bytesRead;
}
}
}
Sample code for this example is available for download here:
SampleWebAttachment Code