one of the great things about IIS on windows is the pluggable architecture for supplying your own http handlers to intercept page requests from your web server...if you wanted to, you could write your own parsing routines from the ground up and implement them as an ISAPI filter, then map page extensions to that filter. one of the bad things about this (pre .Net) is that you had to have a very intricate knowledge of IIS, C++, and ATL (active template library). ATL is a pretty arcane API into IIS for ISAPI stuff, and i personally don't know anyone who has tried to roll their own filters from scratch. that being said, .Net makes this task infinitely more palatable with the introduction of the IHttpHandler interface located in the System.Web namespace. the interface consists of two members, a bool property IsReusable, and a ProcessRequest method that takes an HttpContext parameter and returns void.
when i got punked by microsoft, i needed to come up with a way to keep the name of the link (which was directly to the .rar file, when you navigate to a file with this extension IE will attempt to download the .rar file, if there isn't one there, you will get a 404 error) the same, however not have it download the .rar file. so of course what better solution than to roll my own parser and map it to .rar extension for that particular web directory (this is not a global change, only directory specific). to implement this functionality, there are 3 steps that need to be taken:
- create a class that derives from IHttpHandler and override the 2 members
- add an httpHandlers section in the web.config file that specifies the file extension to map to the type created above, as well as the type and assembly name
- modify the settings in the IIS MMC snap-in to map requests for the file extension to the aspnet_isapi.dll library (unless you really want to write your own).
the solution i wrote is an overly simplified example of what you can do with this technology. in my override of ProcessRequest i simply call Response.Write method of the HttpContext object that is implicitely passed in by IIS when it process any file with the file extension you specify, and in this case it simply says “i got punked by MS”. ultimately i will have something a bit more clever posted (when i have the time to actually think about it). here is what the httpHandlers section looks like in the web.config file:
<httpHandlers>
<add verb="*" path="*.rar" type="WebLogger.PunkedHandler.Punked, WebLogger.PunkedHandler" />
< SPAN>httpHandlers>
here is the class implementing this:
using System;
using System.IO;
using System.Web;
using System.Web.UI;
namespace WebLogger.PunkedHandler
{
///
/// I got punked by Microsoft for my X# demo
///
public class Punked : IHttpHandler
{
private const string link = "http://jaysonknight.com/blog/archive/2004/01/13/205.aspx";
#region IHttpHandler Members
///
/// Method called automatically by IIS when a request to the file extension mapped to this type in the web.config file
///
/// Intrinsic HttpContext object associated with this request
public void ProcessRequest(HttpContext context)
{
// instantiate the user control
PunkedControl lst = new PunkedControl(link);
// new stringwriter object
using (StringWriter textWriter = new StringWriter())
{
// new htmlwriter object
using (HtmlTextWriter writer = new HtmlTextWriter(textWriter))
{
// render the control
lst.RenderControl(writer);
// flush the response to the client
context.Response.Write(textWriter.ToString());
}
}
}
///
/// Specifies whether this instance is reusable by other Http requests
///
public bool IsReusable
{
get
{
return true;
}
}
#endregion
}
}
and here is the custom user control used to output the html stream back to the client:
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebLogger.PunkedHandler
{
///
/// User Control to output an Html stream to the client
///
public class PunkedControl : WebControl
{
private string _link;
///
/// Overloaded constructor for PunkedControl
///
/// The page to link to
public PunkedControl(string linkTo)
{
// the page to link to
_link = linkTo;
}
///
/// Method called when the control starts to render
///
/// HmtlWriter object to output an Html stream to the client
protected override void Render(HtmlTextWriter writer)
{
// I
writer.Write("I");
// space
writer.WriteLine(HtmlTextWriter.SpaceChar);
// begin an anchor tag
writer.WriteBeginTag("a");
// specify the value for the href attribute
writer.WriteAttribute("href", _link);
// closing whicket
writer.Write(HtmlTextWriter.TagRightChar);
// got punked
writer.Write("got punked");
// close the anchor tag
writer.WriteEndTag("a");
// space
writer.Write(HtmlTextWriter.SpaceChar);
// by MS
writer.Write("by MS");
}
}
}
all that's left to do now is add a mapping in IIS. if you right click a virtual directory and go to properties, click on configuration, click on add, then browse to \%system%\microsoft.net\framework\[version]\aspnet_isapi.dll for the executable (we still want the ASP.NET runtime to handle the low level stuff), type in .rar for the extension, specificy the http verbs to handle (in this case GET would suffice), and clear the “check that file exists“ checkbox (in our web.config file we specified *.rar, so you could actually go to jaysonknight.com/blog/misc/[any file name whatsoever].rar, if you leave that checkbox checked and the file isn't really there, you'll get a 404...in this case i actually did remove the .rar file, but we've tricked IIS into thinking it's still there and behaving as an .aspx page). Build it, and you're off to the races!
this has been an extremely high level look at how a custom HttpHandler works in .Net...a couple of people had emailed me asking how i kept the same link to the .rar file, but it displayed as any other web page would without prompting for a download of a file that isn't even there anymore.
happy coding
Posted
Thu, Jan 15 2004 3:42 PM
by
Jayson Knight