Its quite a common scenario to have a feature in a web site to check for the credentials of the user before allowing a particular download. For example, there might be some documents which should be visible only to a set of users and not to any others. This document might be anything - a PDF file, a zip file, an exe, a .doc,etc.
The situation gets even more complicated when you have certain documents that can be downloaded by everyone on the Internet and have some documents visible only to logged in users. A HttpHandler fits this scenario perfectly.
Before I get into developing the HttpHandler, a couple of points regarding the design is a must:
To get on with it, there are three major steps that need to be performed to make sure that the file request is routed through the IIS via the ASPNET pipeline and onto your handler.
Lets take it step by step...and to make it more fun, in the reverse order!
Step 3: Configure IIS
This is quite a straightforward step. All you need to do is open up the properties of the Web Application under consideration.
Step 2: Configure your Web Application
All you need to do is add the below line(s) into the web.config of the application you are developing:
<system.web> <httpHandlers> <add verb="GET" path="*.pdf" type="Microsoft.Web.PortalFramework.HttpHandlers.PdfDownloadAuthorizationHandler, Microsoft.Web.PortalFramework" /> </httpHandlers> </system.web>
The above states that the HTTP verb GET with all files ending in extenion pdf should be routed to the class mentioned in the type. The type if of format "<Namespace.Classname>, <AssemblyName>"
Step 1: Develop your Handler
All you need is for your class to implement IHttpHandler interface in the System.Web namespace and that's its! Instead of saying some blah blah blah about the code, here's the full code:
using System; using System.IO; using System.Collections.Generic; using System.Text; using System.Web; namespace Microsoft.Web.PortalFramework.HttpHandlers { /// <summary> /// /// </summary> public class PdfDownloadAuthorizationHandler: IHttpHandler { private static string userDocsBaseFolder; /// <summary> /// Initializes a new instance of the <see cref="DownloadAuthorizationModule"/> class. /// </summary> public PdfDownloadAuthorizationHandler() { userDocsBaseFolder = "PersonalDocs"; } #region IHttpHandler Members /// <summary> /// Gets a value indicating whether another request can /// use the <see cref="T:System.Web.IHttpHandler"></see> instance. /// </summary> /// <value></value> /// <returns>true if the <see cref="T:System.Web.IHttpHandler"></see> /// instance is reusable; otherwise, false.</returns> public bool IsReusable { get { return false; } } /// <summary> /// Enables processing of HTTP Web requests by a custom HttpHandler /// that implements the <see cref="T:System.Web.IHttpHandler"></see> interface. /// </summary> /// <param name="context">An <see cref="T:System.Web.HttpContext"></see> /// object that provides references to the intrinsic server objects /// (for example, Request, Response, Session, and Server) /// used to service HTTP requests.</param> public void ProcessRequest(HttpContext context) { try { //get the url requested by the user string urlRequested = HttpUtility.UrlDecode(context.Request.Url.AbsolutePath.ToUpper()); //set the initial flag to false bool userPersonalDoc = false; //remove all spaces in the url before checking if the url contains the private path if (urlRequested.Replace(" ", "").Contains(userDocsBaseFolder)) { //once we know that its for the personal folders, set the flag userPersonalDoc = true; //we now assume that the cookie is set with the document profile id of the user //and the dynamic link that its generated includes this key //check only for the negative conditions HttpCookie profCookie = context.Request.Cookies["DocId"]; if (profCookie != null) { //we need to use some sort of encryption (refer my previous post!) TripleDESCryptoHelper helper = new TripleDESCryptoHelper(); string profileId = helper.GetDecryptedValue(profCookie.Value); Guid profileGuid = new Guid(profileId); //check if the docid from the url and the one in the cookie match if (!urlRequested.Contains(profileGuid.ToString("B").ToUpper())) { //no match - spoofed request, send 'em back context.Response.Redirect(context.Request.ApplicationPath,true); } } else { context.Response.Redirect(context.Request.ApplicationPath); } } //if we have reached till here, we are good to go this.TransmitRequestedFile(context, urlRequested, userPersonalDoc); } catch (Exception generalException) { ExceptionManager.LogException(generalException); } } /// <summary> /// Transmits the requested file. /// </summary> /// <param name="context">The context.</param> /// <param name="urlRequested">The URL requested.</param> /// <param name="forceDownload">if set to <c>true</c> [force download].</param> private void TransmitRequestedFile(HttpContext context, string urlRequested, bool forceDownload) { // as a security considerataion, force the user to download the document // if the doc is from the personal folder if (forceDownload) { this.StartForcedDownload(context, urlRequested); } else { //else let the IE handle the filetype this.TransmitNormal(context, urlRequested); } } /// <summary> /// Transmits the file in 'normal' way. /// </summary> /// <param name="context">The context.</param> /// <param name="urlRequested">The URL requested.</param> private void TransmitNormal(HttpContext context, string urlRequested) { string filePath = HttpContext.Current.Server.MapPath(urlRequested); string fileName = System.IO.Path.GetFileName(filePath); context.Response.ClearContent(); context.Response.ClearHeaders(); FileInfo pdfInfo = new FileInfo(filePath); context.Response.AddHeader("Content-Length", pdfInfo.Length.ToString()); //assuming that the file is pdf, needs to be changed appropriately //context.Response.ContentType = "application/zip"; context.Response.ContentType = "application/pdf"; context.Response.TransmitFile(filePath); context.Response.End(); } /// <summary> /// Starts the forced download. /// </summary> /// <param name="context">The context.</param> /// <param name="urlRequested">The URL requested.</param> private void StartForcedDownload(HttpContext context, string urlRequested) { string filePath = HttpContext.Current.Server.MapPath(urlRequested); string fileName = System.IO.Path.GetFileName(filePath); context.Response.ClearContent(); context.Response.ClearHeaders(); context.Response.AddHeader("Content-Disposition", "inline; filename=" + fileName); context.Response.ContentType = "application/pdf"; context.Response.BufferOutput = false; context.Response.TransmitFile(filePath); context.Response.End(); } #endregion } }