Intercepting Downloads in ASP.NET

There might be general situations when developer needs to provide a download link to users for any kind of document that may include but not limited to doc, pdf, zip etc. However, the scenarios do exist when developer wants to authenticate the users before they could even reach at the source of the document, or there might be a requirement to log all the downloads performed on the website. Also the developer might not wish to give a direct URL of the document to any user at any time. Direct URLs may lead to unnecessary overhead, as the authors of other websites may provide a direct link of the document on their website.

It arises the need of a download interceptor that could control, record and monitor the activities of the downloads performed.

For implementing such an interceptor, all we need is the data entity for the documents to be presented to download and an ASPX page that will be requested instead of the original name of the document.

The provided URL for the document to download might look like the following on port 80, where documentId is the document ID or number, which the interceptor will use as reference to lookup the document name from the documents table.

http://yourHost/yourApp/download.aspx?documentId=1

Assuming the document table contains at least two fields, one the document’s ID and the other one its Name. Now let’s see how do we code the download.aspx and its code behind. The front end page doesn’t include anything, just leave it intact.

public class download : System.Web.UI.Page
{
SqlConnection con;
SqlCommand cmd;
string document;

//retrieve static strings from the configuration file.
string connStr=System.Configuration.ConfigurationSettings.AppSettings["dbStr"];
protected string absUploadPath=System.Configuration.ConfigurationSettings.AppSettings["absPath"]+"\\upload",
uploadUrl=System.Configuration.ConfigurationSettings.AppSettings["siteUrl"]+"/upload";

private void Page_Load(object sender, System.EventArgs e)
{
//Authentication routine goes here. You can put your authentication checks here so that no one other than the allowed user accesses this section
if(!IsPostBack)
{
try
{
con=new SqlConnection(connStr);
con.Open();
cmd=new SqlCommand("select documentPath from tblDocuments where dId=@dId",con);
cmd.Parameters.Add("@dId",Request.QueryString["documentId"]);

object obj=cmd.ExecuteScalar();
document=(obj!=null)?obj.ToString():null;
//Authorisation routine can also be put to validate the user against individual documents, just as ACLs

if(System.IO.File.Exists(absUploadPath+"\\"+document) && document.Length!=0)
{
Response.ContentType="application/unknown";
Response.AppendHeader("Content-Disposition","attachment; filename="+document); //attach the file to force download
Response.WriteFile(absUploadPath+"\\"+document);
Response.Flush();
//User user=(User)Session["userDetails"]; //custom class that stores user information in the session
}
else
{
Response.Write("<font face=tahoma size=-1><b>The file you requested doesn't exist </b><br><br> Sit tight, You're being taken back to the User Section.");
Response.AddHeader ("Refresh", "3; URL=previousPage.aspx"); //redirect to the previous page after a delay of 3 seconds
}
}
//In case of any trouble redirect!
catch(SqlException sEx){Response.Write(sEx.Number + " " + sEx.Message); Response.AddHeader ("Refresh", "3; URL=previousPage.aspx");}
catch(Exception ex){Response.Write(ex.Message); Response.AddHeader ("Refresh", "3; URL=previousPage.aspx");}
finally{con.Close();}
}
}

}

The above discussed practice can be customised as per the requirements. The authentication and authorisation routines can be put into operation by retrieving the user information from the session or other state maintenance utility.

Sumit Amar