Export (0) Print
Expand All

Serving Dynamic Content with HTTP Handlers

 

Scott Mitchell

April 2004

Applies to
   Microsoft ASP.NET
   Microsoft Internet Information Services

Summary: Learn how to provide templated URL driven content with HTTP Handlers, using three real-world scenarios presented in this article. (29 printed pages)

Download the source code for this article.

Contents

Introduction
Routing Requests Based on File Extension
Creating and Configuring an HTTP Handler
Dissecting Some Real-World HTTP Handlers
Conclusion
Related Books

Introduction

Whenever a request reaches an Microsoft Internet Information Services (IIS) Web server, IIS determines how to handle the file by examining the requested file's extension. Static files, like HTML pages, images, Cascading Style Sheet (CSS) files, and the like, are handled directly by IIS. Requests for Microsoft ASP.NET Web pages or Web services—files with extensions .aspx or .asmx—are handed off to the ASP.NET engine. Requests for files with the extension .asp are handed off to the classic ASP engine. The ASP.NET and ASP engine are responsible for generating the markup for the requested resource. For ASP.NET and classic ASP Web pages, this markup is HTML; for Web services, the markup is a SOAP response. Once the engine has successfully rendered the markup for the requested resource, this markup is returned to IIS, which then sends the markup back to the client that requested the resource.

This model of serving content—having IIS directly serve only static content, while delegating the rendering of dynamic content to separate engines—has two distinct advantages:

  1. It provides a nice division of labor. IIS can focus on excelling at serving static content, and can leave the details for serving dynamic content to an external program. That is, IIS can focus on efficiently serving HTML pages and images while the ASP.NET engine can focus on efficiently rendering ASP.NET Web pages and Web services.
  2. It allows for new dynamic server-side technologies to be added to IIS in a pluggable manner. Imagine if IIS was responsible for rendering ASP.NET Web pages itself, rather than relying on an external engine. In that case, each time a new version of ASP.NET came out—or any dynamic server-side technology, for that matter—a new version of IIS would need to be created that supported this new version. Those who wanted to use the latest version would have to update the version of IIS.

To have this model of serving content work, IIS needs a mapping of file extensions to programs. This information exists in the IIS metabase and can be configured via the Internet Services Manager, as we'll see in the next section. When a request comes into IIS, then, this mapping is consulted to determine where the request should be routed. Extensions like .aspx, .asmx, .ashx, .cs, .vb, .config, and others are all configured, by default, to be routed to the ASP.NET engine.

Whenever a request is routed from IIS to the ASP.NET engine, the ASP.NET engine performs a similar series of steps to determine how to properly render the requested file. Specifically, the ASP.NET engine examines the requested file's extension and then invokes the HTTP handler associated with that extension, whose job it is to render the requested file's markup.

Note   Technically, the ASP.NET engine will invoke either an HTTP handler or an HTTP handler factory. An HTTP handler factory is a class that returns an instance of an HTTP handler.

An HTTP handler is a class that knows how to render content for a particular type of Web content. For example, there is a different HTTP handler class in the .NET Framework for rendering ASP.NET Web pages than there is for rendering Web services. Just as IIS relies on external programs to serve dynamic content, the ASP.NET engine relies on different classes to render the content for a certain type of content.

By having the ASP.NET engine pluggable like IIS, the same advantages discussed earlier are realized by ASP.NET. Of particular interest is the fact that this model allows for developers to create new HTTP handler classes, and plug them into the ASP.NET engine. In this article, we'll examine precisely how to create custom HTTP handlers and use them in an ASP.NET Web application. We'll start with an in-depth look at how the ASP.NET engine determines what HTTP handler should service the request. We'll then see how to easily create our own HTTP handler classes with just a few lines of code. Finally, we'll look at a number of real-world HTTP handler examples that you can start using in your Web applications today.

Routing Requests Based on File Extension

As discussed in the Introduction, both IIS and the ASP.NET engine route incoming requests to an external program or class based on the request's file extension. In order to achieve this, it is imperative that both IIS and ASP.NET have some sort of directory, mapping file extensions to external programs. IIS stores this information in its metabase, which is able to be edited through the Internet Services Manager. Figure 1 shows a screenshot of the Application Configuration dialog box for an IIS application. Each provided extension maps to a specific executable path. Figure 1 shows some of the file extensions that are mapped to the ASP.NET engine (.asax, .ascx, .ashx, .asmx, and so on).

ms972953.httphandlers_fig01(en-us,MSDN.10).gif

Figure 1. Configured file extensions

Specifically, IIS maps the ASP.NET-related extensions to \WINDOWS_DIR\Microsoft.NET\Framework\VERSION\aspnet_isapi.dll. Just as ASP.NET maps file extensions to HTTP handlers, IIS maps file extensions to ISAPI Extensions. (An ISAPI extension is an unmanaged, compiled class that handles an incoming Web request, whose task is to generate the content for the requested resource.) The ASP.NET engine, however, is a set of managed classes in the .NET Framework. aspnet_isapi.dll serves as a bridge between the unmanaged world (IIS) and the managed world (the ASP.NET engine).

For a more in-depth look at how IIS handles incoming requests, including how to customize the IIS-specific mappings, check out Michele Leroux Bustamante's article Inside IIS and ASP.NET.

While IIS stores its directory of file extensions and ISAPI Extensions in its metabase, this directory for ASP.NET is stored in XML-formatted configuration files. The machine.config file (located in \WINDOWS_DIR\Microsoft.NET\Framework\VERSION\CONFIG\) contains the default, Web server-wide mappings, while the Web.config file can be used to specify mappings specific to a Web application.

In both the machine.config and Web.config files, the mappings are stored in the <httpHandlers> element. Each mapping is represented by a single <add> element, which has the following syntax:

<add verb="verb list" path="extension | path" 
type="HTTP handler type" />

The verb attribute can be used to limit the HTTP handler to only serve particular types of HTTP requests such as GETs or POSTs. To include all verbs, use *. The path attribute specifies an extension to map to the HTTP handler, such as *.scott, or can specify a particular URL path. Finally, the type attribute specifies the type of the HTTP handler that is responsible for rendering this content.

The following is a snippet of default HTTP handler assignments in the machine.config file:

<httpHandlers>
   <add verb="*" path="*.aspx" 
     type="System.Web.UI.PageHandlerFactory" />
   <add verb="*" path="*.asmx" 
type="System.Web.Services.Protocols.WebServiceHandlerFactory, 
System.Web.Services, Version=1.0.5000.0, Culture=neutral, 
PublicKeyToken=b03f5f7f11d50a3a" validate="false" />
   <add verb="*" path="*.ascx" 
     type="System.Web.HttpForbiddenHandler" />
   <add verb="*" path="*.config" 
      type="System.Web.HttpForbiddenHandler" />
   <add verb="*" path="*.cs" 
     type="System.Web.HttpForbiddenHandler" />
   <add verb="*" path="*.vb" 
      type="System.Web.HttpForbiddenHandler" />
   ...
</httpHandlers>

The first <add> element maps all requests to ASP.NET Web pages (*.aspx) to the HTTP handler factory PageHandlerFactory. The second maps all requests to Web services (.asmx) to the WebServiceHandlerFactory class. The remaining four <add> elements map certain extensions to the HttpForbiddenHandler HTTP handler. This HTTP handler, then, is invoked if a user attempts to browse to a .config file, such as your application's Web.config file. The HttpForbiddenHandler simply emits a message indicating that files of that type are not served, as shown in Figure 2.

Note   You can protect individuals from directly accessing sensitive files by mapping those files' extensions to the HttpForbiddenHandler HTTP handler in either the machine.config or Web.config files. For example, if you run a Web hosting company you could configure IIS to route requests to .mdb files (Microsoft Access database files) to the ASP.NET engine, and then have the ASP.NET engine map all .mdb files to the HttpForbiddenHandler. That way, even if your users put their Access database files in a Web-accessible location, nefarious users won't be able to download them. For more information see my article Protecting Files with ASP.NET.

ms972953.httphandlers_fig02(en-us,MSDN.10).gif

Figure 2. Restricting viewing web.config

The machine.config file specifies the default mappings for all Web applications on the Web server. The mappings, however, can be customized on a Web-application by Web-application basis using the Web.config file. To add an HTTP handler to a Web application, add an <add> element to the the <httpHandlers> element. The <httpHandlers> element should be added as a child of the <system.web> element, like so:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
  <system.web>
    <httpHandlers>
        <add verb="verb list" path="extension | path" type="type" />
    </httpHandlers>

    ...
  </system.web>
</configuration>

Specific HTTP handlers can also be removed from a Web-application using the <remove> element like so:

    <httpHandlers>
        <remove verb="verb list" path="extension | path" />
    </httpHandlers>

When customizing the ASP.NET engine's mapping of file extensions to HTTP handlers, it is important to understand that the file extensions being set in the machine.config or Web.config files must be mapped to the aspnet_isapi.dll in the IIS metabase. In order for the ASP.NET engine to be able to route a request to the proper HTTP handler, it must first receive the request from IIS. IIS will route the request to the ASP.NET engine only if the requested file's extension is mapped to the aspnet_isapi.dll file in the IIS metabase. This is something you'll always need to keep in mind when creating custom HTTP handlers. We'll see an example later in this article where an addition needs to be made to the IIS metabase, mapping the .gif and .jpg extensions to the aspnet_isapi.dll ISAPI Extension.

Now that we've examined how IIS maps incoming requests to ISAPI Extensions, and how the ASP.NET engine maps incoming requests to HTTP handlers (or HTTP handler factories), we're ready to examine how to create our own HTTP handler classes.

Creating and Configuring an HTTP Handler

Adding an HTTP handler to an ASP.NET Web application requires two steps. First, the HTTP handler must be created, which entails creating a class that implements the System.Web.IHttpHandler interface. Second, the ASP.NET Web application needs to be configured to use the HTTP handler. In the previous section we saw how to configure a Web application to use an HTTP handler—by adding an <httpHandlers> section to the application's Web.config file or the Web server's machine.config file. Since we've already looked at configuring an application to use an HTTP handler, let's focus on building an HTTP handler.

The IHttpHandler interface defines one method, ProcessRequest(HttpContext), and one property, IsReusable. The ProcessRequest(HttpContext) method takes in a System.Web.HttpContext instance which contains information about the request. It is the ProcessRequest(HttpContext) method's responsibility to emit the correct markup based on the request details.

The HttpContext class that is passed into the ProcessRequest(HttpContext) method exposes many of the same vital properties that the System.Web.UI.Page class provides: the Request and Response properties allow you to work with the incoming request and outgoing response; the Session and Application properties can be used to work with session and application state; the Cache property provides access to the application's data cache; and the User property contains information about the user making the request.

Note   The seeming similarities between the HttpContext and Page classes are not coincidental. The Page class, in fact, an HTTP handler itself, implements IHttpHandler. At the start of the Page class's ProcessRequest(HttpContext) method the Page class's Request, Response, Server, and other intrinsic objects are assigned the corresponding properties of the passed-in HttpContext.

The IsReusable property of the IHttpHandler is a Boolean property that indicates if the HTTP handler is reusable. The IsReusable property indicates if one instance of the HTTP handler can be used for other requests to the same file type, or if each request requires an individual instance of the HTTP handler class. Sadly, there is little information in the official documentation as to what are the best practices for using IsReusable. This ASP.NET Forums post, by Dmitry Robsman, a developer on the ASP.NET Team at Microsoft, sheds some light on the subject: "A handler is reusable when you don't need a new instance for each request. Allocating memory is cheap, so you only need to mark a handler as reusable if one-time initialization cost is high." Dmitry also points out that the Page class—which is an HTTP handler, recall—is not reusable. With this information, you can feel confident to have your HTTP handler return false for IsReusable.

Creating a Simple HTTP Handler

To illustrate creating an HTTP handler class, let's build a simple HTTP handler, one that merely displays the current time along with request information. To follow along, create a new Class Library project in your language of choice (I'll be using C#; I named my project skmHttpHandlers). This will create a new project with a default Class.cs (or Class.vb) file. In this file, add the following code (your namespace might differ):

using System;
using System.Web;

namespace skmHttpHandlers
{
   public class SimpleHandler : IHttpHandler
   {
      public void ProcessRequest(HttpContext context)
      {
         // TODO:  Add SimpleHandler.ProcessRequest implementation
      }

      public bool IsReusable
      {
         get
         {
            // TODO:  Add SimpleHandler.IsReusable 
            // getter implementation
            return false;
         }
      }
   }
}

The code above defines a class called SimpleHandler that implements IHttpHandler. The SimpleHandler class provides a single method—ProcessRequest(HttpContext)—and a single property—IsReusable. We can leave the IsReusable property as-is (since it returns false by default), meaning that all we have left to do is write the code for the ProcessRequest(HttpContext) method.

We can have this HTTP handler render the current time and request details by adding the following code to the ProcessRequest(HttpContext) method:

public void ProcessRequest(HttpContext context)
{
   context.Response.Write("<html><body><h1>The current time is ");
   context.Response.Write(DateTime.Now.ToLongTimeString());
   context.Response.Write("</h1><p><b>Request Details:</b><br /><ul>");
   context.Response.Write("<li>Requested URL: ");
   context.Response.Write(context.Request.Url.ToString());
   context.Response.Write("</li><li>HTTP Verb: ");
   context.Response.Write(context.Request.HttpMethod);
   context.Response.Write("</li><li>Browser Information: ");
   context.Response.Write(context.Request.Browser.ToString());
   context.Response.Write("</li></ul></body></html>");
}

Notice that this code emits its content using a series of Response.Write() statements, spitting out the precise HTML markup that should be sent back to the requesting Web browser. Realize that the goal of the ProcessRequest(HttpContext) method is to emit the markup for the page to the Response object's output stream. A simple way to achieve this is through Response.Write(). (In the "Protecting Your Images" section we'll see how to write the binary content of image files directly to the Respose object's OutputStream property.)

What About Web Controls?

For HTTP handlers that render HTML markup, you might be a bit remiss if you use Response.Write() statements. You'll want to use Web controls to emit HTML markup instead. You can use Web controls in an HTTP handler, although it's not as simple or straightforward as using Web controls in an ASP.NET Web page. There are two techniques that can be employed:

  1. Create a separate ASP.NET Web page that serves as a template for the HTTP handler's output. The HTTP handler, then, marries the template and the dynamic content to display.
  2. Create the Web controls programmatically in the HTTP handler and use the RenderControl() method to render the HTML markup of the Web control(s).

The first approach is the ideal one as it provides a clean separation of code and content. That is, the HTML markup an HTTP handler generates can be tweaked by just modifying the template ASP.NET Web page rather than mucking around with Response.Write() statements in the HTTP handler. It takes a bit of work to get this technique working properly, though. In the "Using an HTTP Handler Factory for Displaying URL-Driven Content" section we'll look at an HTTP handler factory that uses this technique.

The second technique involves programmatically creating instances of the Web control classes that you want to have rendered in the ProcessRequest() method ofyour HTTP handler. This technique proves a bit challenging since if you want to add Web controls nested inside of other ones, you have to manually build the Web control hierarchy yourself. Once the control hierarchy has been constructed, you need to generate the HTML for the control hierarchy using the RenderControl() method.

The following code snippet illustrates how Web controls can be rendered programmatically in an HTTP handler:

public void ProcessRequest(HttpContext context)
{
   // build up the control hiearchy - a Panel as the root, with
   // two Labels and a LiteralControl as children...
   Panel p = new Panel();      

   Label lbl1 = new Label();
   lbl1.Text = "Hello, World!";
   lbl1.Font.Bold = true;

   Label lbl2 = new Label();
   lbl2.Text = "How are you?";
   lbl2.Font.Italic = true;

   p.Controls.Add(lbl1);
   p.Controls.Add(new LiteralControl(" - "));
   p.Controls.Add(lbl2);

   // Render the Panel control
   StringWriter sw = new StringWriter();
   HtmlTextWriter writer = new HtmlTextWriter(sw);
   p.RenderControl(writer);

   // Emit the rendered HTML
   context.Response.Write(sw.ToString());
}

(For the above code to work the System.IO, System.Web, System.Web.UI, and System.Web.UI.WebControls namespaces will need to be included via Imports or using statements.)

Clearly building and rendering a control hierarchy by hand is not nearly as easy as adding Web controls by dragging and dropping, or using the declarative Web control syntax. Realize that each time an ASP.NET Web page is visited after its HTML portion has been changed, the HTML portion is converted into a class that programmatically builds up the control hierarchy, akin to our example above.

Configuring an ASP.NET Web Application to Use the Simple HTTP Handler

After creating the HTTP handler class, all that remains is configuring an ASP.NET Web application to use the handler for a particular file extension. Assuming you created a new Microsoft® Visual Studio® .NET Solution for the HTTP handler Class Library project, the easiest approach is to add a new ASP.NET Web application project to the Solution. You'll also need to add the HTTP handler project to the ASP.NET Web application's References folder. (Right-click on the References folder and choose Add Reference. From the Add Reference dialog box, select the Projects tab and pick the Class Library project created in the previous section.) If you are not using Visual Studio .NET you'll need to manually copy the HTTP handler's assembly to the /bin directory of the ASP.NET Web application.

Recall that to configure a Web application to use an HTTP handler we need to map some file extension (or a specific path) to the HTTP handler. We could make up our own extension, like .simple, but in doing so we'd have to configure IIS to map the .simple extension to the ASP.NET engine's ISAPI Extension (aspnet_isapi.dll). If you are hosting your site on a shared Web server, chances are the Web hosting company doesn't allow you to add custom mappings to the IIS metabase. Fortunately, when the .NET Framework is installed on a Web server the extension .ashx is automatically added and mapped to the ASP.NET engine's ISAPI Extension. This extension, then, can be used for custom HTTP handlers if you do not have access or permissions to modify the IIS metabase.

For this first example of configuring a Web application to use a custom HTTP handler, let's use the .ashx extension, which is accomplished by adding the following <httpHandlers> section to the Web application's Web.config file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <httpHandlers>
       <!-- Simple Handler -->
       <add verb="*" path="*.ashx" 
         type="skmHttpHandlers.SimpleHandler, skmHttpHandlers" />
    </httpHandlers>
  </system.web>
</configuration>

Notice that the <add> element indicates that a request coming in on any HTTP verb for any file with a .ashx extension should be handled by the skmHttpHandlers.SimpleHandler HTTP handler. The type attribute's value specifies the type of the HTTP handler class to use (namespace.className), followed by a comma, and then followed by the assembly name where the HTTP handler class resides.

After adding the above <httpHandlers> section to the Web application's Web.config file, visiting any path with a .ashx extension displays the page shown in Figure 3.

ms972953.httphandlers_fig03(en-us,MSDN.10).gif

Figure 3. Simple HTTP handler

Figure 3 shows a screenshot of a browser visiting the Web application's HelloWorld.ashx file. Realize that this file, HelloWorld.ashx, does not actually exist. What happens is the following:

  1. The request comes into IIS for HelloWorld.ashx.
  2. IIS notes the .ashx extension and routes the request to the ASP.NET engine.
  3. The ASP.NET engine consults the Web.config file and notices that .ashx files should be handled by the SimpleHandler HTTP handler. The ASP.NET engine, then, creates an instance of this class and calls its ProcessRequest() method, passing in the current HttpContext.
  4. The SimpleHandler HTTP handler emits the current time and request details to the Response object's output stream.
  5. The ASP.NET engine returns the rendered HTML markup from the HTTP handler to IIS.
  6. IIS returns the rendered HTML markup to the browser that requested HelloWorld.ashx.
  7. The browser displays the rendered HTML, as seen in Figure 3.

These seven steps would proceed in the same manner had the visitor requested ASPisNeat.ashx, myfile.ashx, or any file with an .ashx extension.

Dissecting Some Real-World HTTP Handlers

Now that we have looked at the steps necessary for creating an HTTP handler and configuring a Web application to use the handler, let's examine some realistic HTTP handlers that you can start using in your ASP.NET Web applications today. (Many of these HTTP handler ideas come from comments on a blog entry I made requesting suggestions for real-world HTTP handler examples.)

The remainder of this article walks through three such HTTP handlers:

  • CodeFormatHandler—an HTTP handler that formats code files (.cs and .vb files) similar to Visual Studio .NET. (The handler only displays the code file if the request comes through localhost.)
  • ImageHandler—an HTTP handler that serves up GIF and JPEG images. The handler adds a watermark to the image and checks to ensure that the image is not being "lifted" from your site (that is, that someone from another site is not linking to your image from their Web site).
  • EmployeeHandlerFactory—an HTTP handler factory that works with a separate ASP.NET Web page to display information about a specific employee from an employee database.

The complete source for all three handler examples—along with the source code for the ASP.NET Web application demonstrating their use—is available to download from this article.

Formatting Code with an HTTP Handler

Have you ever wanted to quickly see the source code for one of your ASP.NET Web page's code-behind classes, but didn't want to have to take the time to load up Visual Studio .NET and open the associated project? If you're like me, you likely already have several instances of Visual Studio .NET open, along with Microsoft® Outlook, Microsoft® Word, the .NET Framework documentation, SQL Enterprise Manager, and a Web browser, so opening another instance of Visual Studio .NET is usually the last thing I want to do.

Ideally, it would be nice to be able to visit the code-behind class that resides on the server to view its source. That is, to see the code for the page WebForm1.aspx, I could just point my browser to http://localhost/WebHost1.aspx.cs. If you try this, however, you'll get the "This type of page is not served" method (see Figure 2) since, by default, the .cs and .vb extensions are mapped to the HttpForbiddenHandler HTTP handler. This is a good thing, mind you, since your code-behind classes may contain connection strings or other sensitive information that you don't want to allow any random visitor to view. Ideally, though, when moving the ASP.NET Web application from a development server to a production server you'd not copy over the code-behind class files—just the .aspx files and the required assemblies in the /bin directory.

On your development server, though, you might want to be able to view the code-behind source code through a browser. One option would be to just remove the mapping from .cs and .vb files to the HttpForbiddenHandler HTTP handler. (This could be done for the entire development server by modifying the machine.config file, or on an application-by-application basis by using a <remove> element in the <httpHandlers> section of the Web.config file.) When this works, the source code is displayed as plain text, in an unformatted manner (see Figure 4).

ms972953.httphandlers_fig04(en-us,MSDN.10).gif

Figure 4. Displaying code with an HTTP handler

While this definitely works, the source code display is anything but ideal. Fortunately there are free .NET libraries available that perform HTML formatting of code, such as squishySyntaxHighlighter by squishyWARE. With just a couple of lines of code, squishySyntaxHighlighter takes in a string containing Visual Basic.NET code, C# code, or XML content and returns a string of HTML that displays the passed-in content akin to how Visual Studio .NET renders code and XML content. To accomplish this we will use an HTTP handler. This is, after all, what HTTP handlers are designed to do—to render a specific type of content. In this case we're providing a formatted rendering of code-behind classes.

The following code shows the ProcessRequest() method of the CodeFormatHandler HTTP handler. The SyntaxHighlighter class used in the code is the class provided by squishySyntaxHighlighter. To highlight code all we have to do is create an instance of the SyntaxHighlighter class using the GetHighlighter() static method, specifying how we want the code to be formatted (as Visual Basic.NET, C#, or XML). Then, the created instance's Highlight(contents) method takes in a string input (contents) and returns an HTML-formatted representation of that content. At the end of this method, the formatted content is emitted using Response.Write().

public void ProcessRequest(HttpContext context)
{
   string output = string.Empty;

   // grab the file's contents
   StreamReader sr = File.OpenText(context.Request.PhysicalPath);
   string contents = sr.ReadToEnd();
   sr.Close();

   // determine how to format the file based on its extension
   string extension = Path.GetExtension(
     context.Request.PhysicalPath).ToLower();
   SyntaxHighlighter highlighter;

   if (extension == ".vb")
   {
      highlighter = SyntaxHighlighter.GetHighlighter( 
        SyntaxType.VisualBasic );
      output = highlighter.Highlight( contents );
   }
   else if (extension == ".cs")
   {
      highlighter = SyntaxHighlighter.GetHighlighter( 
        SyntaxType.CSharp );
      output = highlighter.Highlight( contents );
   }
   else // unknown extension
   {
      output = contents;
   }

   // output the formatted contents
   context.Response.Write("<html><body>");
   context.Response.Write(output);
   context.Response.Write("</body></html>");
}

Figure 5 shows a screenshot of the same code in Figure 4, but when highlighted using the CodeFormatHandler.

ms972953.httphandlers_fig05(en-us,MSDN.10).gif

Figure 5: Code formatted by HTTP handler

Note   The CodeFormatHandler HTTP handler available for download has a slightly more robust ProcessRequest() method, allowing only those accessing the page through localhost to view the code-behind class's source code.

Configuring the HTTP handler in a Web application requires just adding <add> elements to the <httpHandlers> section of the application's Web.config file (or the machine.config file, if you wish to use the handler for the entire Web server). As the following illustrates, both the .cs and .vb extensions are routed to the CodeFormatHandler (as opposed to the HttpForbiddenHandler):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <httpHandlers>
       <!-- Code Format handler -->
       <add verb="*" path="*.cs" 
         type="skmHttpHandlers.CodeFormatHandler, 
         skmHttpHandlers" />
       <add verb="*" path="*.vb" 
         type="skmHttpHandlers.CodeFormatHandler, 
         skmHttpHandlers" />
    </httpHandlers>

    ...
  </system.web>
</configuration>

Protecting Your Images

There's a saying about content on the Web: half of the content is original, the other half is stolen. Computers make it very easy to take the work of others and replicate it with minimal effort. For example, a professional photographer might want to display some of his best images on his Web site, but wants to prevent other people from simply saving the picture and putting it up on their Web site, as if it were their own work. Even if you don't mind if other sites use your images, you want to make sure that they save your images on their site, rather than simply adding an <img> tag that points back to your Web server. (An example would be if site www.bandwidthThief.com had a Web page with an <img> tag like: <img src="http://yourSite.com/BigImage.jpg">. It would be nice to prevent this, since by serving an image from your Web server, you'll have to burden the bandwidth expense, even though the user viewing the image is visitng the www.bandwidthThief.com Web site.)

To help protect images, let's create an HTTP handler that does two things:

  1. Checks to make sure another site is not linking to the requested image.
  2. Adds a watermark to the image.

When an image is requested, most browsers send the URL of the Web page that contains the image in the referrer HTTP header. What we can do in our HTTP handler, then, is check to make sure that the host of the referrer HTTP header and the host of the image URL are the same. If they are not, then we have a bandwidth thief on our hands. In this case, rather than returning the original image (or even the watermarked image), we'll return an alternate image, something like, "YOU CAN VIEW THIS IMAGE BY GOING TO www.YourSite.com."

Note   Some browsers do not send a referrer HTTP header when requesting images; others provide an option to disable this feature. Therefore, this technique is not foolproof, but will likely work for the vast majority of Web surfers, thereby dissuading nefarious Web site designers from linking to images directly on your Web site.

To add a watermark we'll use the System.Drawing namespace classes to add a text message in the center of the image. The .NET Framework contains a number of classes in the System.Drawing namespace that can be used to create and modify graphic images at runtime. Unfortunately, a thorough discussion of these classes is far beyond the scope of this article, but a good starting place for more information is Chris Garrett's articles covering GDI+ and System.Drawing.

Creating the ImageHandler HTTP Handler

The following code snippet shows the ProcessRequest() method for the ImageHandler HTTP handler.

public void ProcessRequest(HttpContext context)
{
   if (context.Request.UrlReferrer == null || 
      context.Request.UrlReferrer.Host.Length == 0 ||
      context.Request.UrlReferrer.Host.CompareTo(
        context.Request.Url.Host.ToString()) == 0)
   {
      // get the binary data for the image
      Bitmap bmap = new Bitmap(context.Request.PhysicalPath);

      // determine if we need to add a watermark
      if (ImageConfiguration.GetConfig().AddWatermark)
      {
         // Create a Graphics object from the bitmap instance
         Graphics gphx = Graphics.FromImage(bmap);

         // Create a font
         Font fontWatermark = new Font("Verdana", 8, FontStyle.Italic);

         // Indicate that the text should be 
         // center aligned both vertically
         // and horizontally...
         StringFormat stringFormat = new StringFormat();
         stringFormat.Alignment = StringAlignment.Center;
         stringFormat.LineAlignment = StringAlignment.Center;

         // Add the watermark...
         gphx.DrawString(ImageConfiguration.GetConfig().WatermarkText, 
                        fontWatermark, Brushes.Beige, 
                        new Rectangle(10, 10, bmap.Width - 10, 
                          bmap.Height - 10), 
                        stringFormat);

         gphx.Dispose();
      }


      // determine what type of file to send back   
      switch (
        Path.GetExtension(context.Request.PhysicalPath).ToLower())
      {
         case ".gif":
            context.Response.ContentType = "image/gif";
            bmap.Save(context.Response.OutputStream, ImageFormat.Gif);
            break;

         case ".jpg":
            context.Response.ContentType = "image/jpeg";
            bmap.Save(context.Response.OutputStream, ImageFormat.Jpeg);
            break;
      }

      bmap.Dispose();
   }
   else
   {
      string imgPath = 
        context.Server.MapPath( 
        ImageConfiguration.GetConfig().ForbiddenFilePath);
      Bitmap bmap = new Bitmap(imgPath);

      // determine what type of file to send back   
      switch (Path.GetExtension(imgPath))
      {
         case ".gif":
            context.Response.ContentType = "image/gif";
            bmap.Save(context.Response.OutputStream, ImageFormat.Gif);
            break;

         case ".jpg":
            context.Response.ContentType = "image/jpeg";
            bmap.Save(context.Response.OutputStream, ImageFormat.Jpeg);
            break;
      }

      bmap.Dispose();   
   }
}

The ImageHandler works in tandem with an ImageConfiguration class, which contains configuration information used by the HTTP handler. The ImageConfiguration class is populated by its static GetConfig() method, which deserializes the appropriate Web.config section. The ImageConfiguration has three properties:

  • FobiddenFilePath—a path to the file which is to be displayed in place of the requested image, if the image is being requested from a different Web site.
  • AddWatermark—a Boolean indicating if all images should be watermarked or not.
  • WatermarkText—the text to display as the watermark, such as "Copyright Scott Mitchell."

These settings are specified in the Web application's Web.config file in the <ImageHandler> section, as shown below:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
  <configSections>
     <section name="ImageHandler"
       type= 
         "skmHttpHandlers.Config.ImageConfigSerializerSectionHandler, 
         skmHttpHandlers" />
  </configSections>
  
  <ImageHandler>    
    <forbiddenFilePath>
       ~/images/STOP-STEALING-MY-BANDWIDTH.gif
    </forbiddenFilePath>
    <addWatermark>true</addWatermark>
    <watermarkText>Copyright Scott Mitchell</watermarkText>
  </ImageHandler>
    
  <system.web>
    ...
  </system.web>
</configuration>

The above settings dictate that if an image is detected as being requested from an external site, the end user will see the image ~/images/STOP-STEALING-MY-BANDWIDTH.gif. Furthermore, the settings indicate that all images should be watermarked with the text "Copyright Scott Mitchell."

The ProcessRequest() method starts out by checking to see if this image is being requested from a remote host by determining if the referrer HTTP header's Host and the image URL's Host match up. If not, then the image is being requested from a different Web server, and the user is redirected to the image specified in the ImageConfiguration class's ForbiddenFilePath property. Otherwise, the code checks to see if the AddWatermark property is true and, if it is, watermarks the image using the specified WatermarkText.

Configuring a Web Application to Use the ImageHandler HTTP Handler

To use the ImageHandler HTTP handler in an ASP.NET Web application you'll need to first add the ImageConfiguration properties through the Web.config in the <ImageHandler> section, and then include an <add> element in the c section associating the .gif and .jpg extensions with the HTTP handler. We examined the <ImageHandler> section above, so let's just look at the <httpHandlers> section. As the following shows, you need two <add> elements—one mapping .gif files to the handler, and one mapping .jpg files.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  ... <ImageHandler> section ...
    
  <system.web>
    <httpHandlers>
        <!-- ImageHandler handlers -->
        <add verb="*" path="*.jpg" 
         type="skmHttpHandlers.ImageHandler, skmHttpHandlers" />
        <add verb="*" path="*.gif" 
         type="skmHttpHandlers.ImageHandler, skmHttpHandlers" />
    </httpHandlers>
    
    ...
  </system.web>
</configuration>

In addition to adding the <httpHandlers> section to your Web.config file you must also configure IIS to send all requests to .gif and .jpg files to the aspnet_isapi.dll ISAPI Extension. If you forget to do this, then anytime a request comes in for a GIF or JPEG file, IIS will handle the request itself. We need to add this mapping to the IIS metabase so that when a GIF or JPEG request comes in, it is routed to the ASP.NET engine, which will then route the request to the ImageHandler HTTP handler.

Now that we've examined how to create the HTTP handler and configure a Web application (and IIS) to use the handler, let's see the handler in action! Figure 6 shows a Web page displaying two images of my dog Sam. Notice that the images are watermarked with the specified watermark text.

ms972953.httphandlers_fig06(en-us,MSDN.10).gif

Figure 6. Watermarked images

Figure 7 shows a Web page that attempts to access an image from another server. For this example, the Web page contains an <img> tag in the HTML like <img src="http://mitchellsvr/HttpHandlerTest/images/SamSitting.jpg">. (mitchellsvr is the name of my computer.) When the page is requested through http://localhost/HttpHandlerTest/WebForm1.aspx, the browser sends the referrer HTTP header as http://localhost/HttpHandlerTest/WebForm1.aspx, while the image's requested URL is http://mitchellsvr/HttpHandlerTest/images/SamSitting.jpg. The ImageHandler HTTP handler detects a difference between the referrer Host and the image URL's Host, and therefore displays the STOP-STEALING-MY-BANDWIDTH.gif image.

ms972953.httphandlers_fig07(en-us,MSDN.10).gif

Figure 7. Limiting image requests

Using an HTTP Handler Factory for Displaying URL-Driven Content

All Web developers have created a single page that displays different data based on some set of parameters, such as a Web page that displays information about an employee based on the employee ID passed through the querystring. While offering a /DisplayEmployee.aspx?EmpID=EmployeeID Web page is one way to show employee information, you might want to be able to provide employee information using a more memorable URL, like /employees/name.info. (With this alternate URL, to see information about employee Jisun Lee you'd visit /employees/JisunLee.info.)

There are a couple of techniques that can be used to achieve a more readable and memorable URL. The first is to use URL rewriting. In a previous article of mine, URL Rewriting in ASP.NET, I showed how to perform URL rewriting using HTTP modules and HTTP handlers. URL rewriting is the process of intercepting a Web request to some non-existing URL and rerouting the request to an actual URL. URL rewriting is commonly used for providing short and memorable URLs. For example, an eCommerce Web site might have a page titled /ListProductsByCategory.aspx, which listed all products for sale in a specific category, where the category of products to display was indicated by a querystring parameter. That is, /ListProductsByCategory.aspx?CatID=PN-1221 might list all widgets for sale. With URL rewriting you could define a "pseudo" URL like /Products/Widgets.aspx, which really doesn't exist. When a request comes into the ASP.NET engine for /Products/Widget.aspx, URL rewriting would, transparently, redirect the user to /ListProductsByCategory.aspx?CatID=PN-1221. The user, however, would still see /Products/Widgets.aspx in their browser's Address bar. URL rewriting in the ASP.NET engine is typically accomplished by using the RewritePath(newURL) method of the HttpContext class, which can be employed in either an HTTP module or an HTTP handler.

The second technique is to create an HTTP handler that knows how to render .info files. This HTTP handler would display employee information by examining the requested URL and picking out the employee's name. Having the employee's name, a quick lookup to a database table would retrieve information about the employee in question. The final step would be to somehow render this information as HTML markup.

In the ImageHandler example we saw how an HTTP handler can inspect the URL of the requested resource, so picking out the employee's name and accessing her information from a database should be relatively straightforward. The real challenge lies in rendering the employee information. The simplest approach would be to hard-code the HTML output in the HTTP handler, using Response.Write() statements to emit the precise HTML markup, inserting the employee's information where needed. (This behavior is akin to that of the first HTTP handler example we looked at, SimpleHandler.)

A better approach is to use an ASP.NET page as a template to separate the code and content. To accomplish this you'll need to first create an ASP.NET Web page in your Web application. This page should have a mix of HTML markup and Web controls, like any other ASP.NET Web page. For our example, we'll be displaying an employee's name, social security number, and a brief biography. This page will therefore have three labels for these three fields. Figure 8 shows a screenshot of the ASP.NET Web page DisplayEmployee.aspx, when viewed in the Design tab in Visual Studio .NET.

ms972953.httphandlers_fig08(en-us,MSDN.10).gif

Figure 8. Employee Information page

Next, we need to create an HTTP handler factory. An HTTP handler factory is a class that implements the System.Web.IhttpHandlerFactory interface, and is responsible for returning an instance of a class that implements IHttpHandler when invoked. A Web application is configured to use an HTTP handler factory in the exact same way that it is configured to use an HTTP handler. Once a Web application maps an extension to an HTTP handler factory, when a request comes in for that extension the ASP.NET engine asks the HTTP handler factory for an IHttpHandler instance. The HTTP handler factory provides such an instance to the ASP.NET engine, which can then invoke this instance's ProcessRequest() method. The ASP.NET engine requests an IHttpHandler instance by calling the HTTP handler factory's GetHandler() method.

For this example, the GetHandler() method needs to do two things: first, it needs to determine the name of the employee being requested and retrieve his employee information. Following that, it must return an HTTP handler that can serve the ASP.NET Web page we created earlier (DisplayEmployee.aspx). Fortunately we don't need to create an HTTP handler to serve the ASP.NET Web page—there already exists one in the .NET Framework that can be retrieved calling the System.Web.UI.PageParser class's GetCompiledPageInstance() method. This method takes in three inputs: the virtual path to the requested Web page, the physical path to the requested Web page, and the HttpContext used in requesting this Web page. The full details of how GetCompiledPageInstance() works are not essential to understand; realize, though, that the method compiles the ASP.NET HTML portion into a class, if needed, and returns an instance of this class. (This autogenerated class is derived from your code-behind class, which is derived from the System.Web.UI.Page class. The Page class implements IHttpHandler, so the class being returned from GetCompiledPageInstance() is an HTTP handler.)

The last challenge facing us is how to pass along the employee information from the HTTP handler factory to the ASP.NET Web page template. The HttpContext object contains an Items property which can be used to pass information between resources that share the same HttpContext. Therefore, we'll want to store the employee's data here.

The last step is to return to the ASP.NET Web page template (DisplayEmployee.aspx). In the template's code-behind class we need to retrieve the employee information from the Items property of the HttpContext and assign the employee data to the respective Label Web controls in the page's HTML portion.

For the demo included with this code's download, I provide a set of classes in the EmployeeBOL project that provide a class representation of an employee (Employee), along with a class for generating employees based on their name (EmployeeFactory). The HTTP handler factory's GetHandler() method is shown below. Notice that it first determines the employee's name and adds the Employee object returned by EmployeeFactory.GetEmployeeByName() to the Items collection of HttpContext. Finally, it returns the IHttpHandler instance returned by PageParser.GetCompiledInstance(), when passing in the DisplayEmployee.aspx ASP.NET Web page template as the physical file path.

public class EmployeeHandlerFactory : IHttpHandlerFactory
{
   ...

   public IHttpHandler GetHandler(HttpContext context, 
     string requestType, string url, string pathTranslated)
   {
      // determine the employee's name
      string empName = 
        Path.GetFileNameWithoutExtension( 
        context.Request.PhysicalPath);

      // Add the Employee object to the Items property
      context.Items.Add("Employee Info", 
        EmployeeFactory.GetEmployeeByName(empName));

      // Get the DisplayEmployee.aspx HTTP handler
      return PageParser.GetCompiledPageInstance(url, 
        context.Server.MapPath("DisplayEmployee.aspx"), context);
   }
}

The code-behind class for the DisplayEmployee.aspx ASP.NET Web page template accesses the Employee class instance from the Items property of HttpContext and assigns the Label Web controls' Text properties to the corresponding Employee properties.

public class DisplayEmployee : System.Web.UI.Page
{
   // three Label Web controls in HTML portion of page
   protected System.Web.UI.WebControls.Label lblName;
   protected System.Web.UI.WebControls.Label lblSSN;
   protected System.Web.UI.WebControls.Label lblBio;

   private void Page_Load(object sender, System.EventArgs e)
   {
      // load Employee information from context
      Employee emp = (Employee) Context.Items["Employee Info"];

      if (emp != null)
      {
         // Assign the Employee properties to the Label controls
         lblName.Text = emp.Name;
         lblSSN.Text = emp.SSN;
         lblBio.Text = emp.Biography;
      }
   }
}

To configure the Web application to use the HTTP handler factory, place an <add> element in the <httpHandlers> section, just like you would for an HTTP handler:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <httpHandlers>
      <!-- EmployeeHandlerFactory -->
      <add verb="*" path="*.info" 
         type="skmHttpHandlers.EmployeeHandlerFactory, 
         skmHttpHandlers" />
    </httpHandlers>
  </system.web>
</configuration>

Of course, since this HTTP handler factory uses the .info extension, you'll also need to map the .info extension in IIS to the aspnet_isapi.dll ISAPI Extension. Figure 9 shows a screenshot of the employee HTTP handler factory in action.

ms972953.httphandlers_fig09(en-us,MSDN.10).gif

Figure 9. Using the Employee Information handler

The benefit of this template approach is that if we wanted to change the Employee Information page, we'd just need to edit the DisplayEmployee.aspx page. There'd be no need to alter the HTTP handler factory, or to have to recompile the HTTP handler factory assembly.

Note   .Text, an open-source blog engine written in C# by Scott Watermasysk, uses HTTP handler factories to provide URL driven content. .Text provides a much more in-depth template system than the one I presented in this article. It has a single master template page, DTP.aspx, which is used as the template for all requests. This template, though, can be customized based on the type of request made. For example, if a request is made to a URL like /blog/posts/123.aspx, .Text can determine that based on the URL path (/posts/) the user is asking to view a particular post. Therefore, the master template page is customized (at runtime) by loading a set of User Controls specific to displaying a single blog post. If, on the other hand, a request comes in for /blog/archive/2004/04.aspx, .Text can determine based on the path (/archive/2004/XX.aspx) that you want to view all posts for a given month (April 2004 in this example). The master template page will therefore have those User Controls loaded which are pertinent to displaying a month's entries.

Conclusion

Like ISAPI Extensions in IIS, HTTP handlers provide a level of abstraction between the ASP.NET engine and the rendering of Web content. As we saw, an HTTP handler is responsible for generating the markup for a particular type of request, based on a specified extension. HTTP handlers implement the IHttpHandler interface, providing all the heavy lifting in the ProcessRequest() method.

This article looked at three real-world HTTP handler scenarios: a code formatter, an image protector, and an HTTP handler factory for providing templated, URL driven content. Some other uses for HTTP handlers include:

  • Adding variables to external CSS files (see this blog post by Rory Blyth for more information).
  • Displaying a list of thumbnail images (idea shared by Andrew Connell in this blog comment).
  • Optimizing external JavaScript files by stripping whitespace and comments, variable renaming, etc.

If you come up with other cool ideas or uses for HTTP handlers, I invite your comments at this blog entry: REQUEST: Ideas for a Useful HTTP Handler Demo?

Happy Programming!

Related Books

 

About the Author

Scott Mitchell, author of five books and founder of 4GuysFromRolla.com, has been working with Microsoft Web technologies for the past six years. Scott works as an independent consultant, trainer, and writer. He can be reached at mitchell@4guysfromrolla.com or via his blog, which can be found at http://ScottOnWriting.NET.

    Show:
    © 2014 Microsoft