Chapter 30

Modules and Handlers

WHAT’S IN THIS CHAPTER?

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

Please note that all the code examples in this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com on the Download Code tab.

Sometimes, just creating dynamic web pages with the latest languages and databases does not give you, the developer, enough control over an application. At times, you need to be able to dig deeper and create applications that can interact with the web server itself. You want to be able to interact with the low-level processes, such as how the web server processes incoming and outgoing HTTP requests.

Before ASP.NET, to get this level of control using IIS, you were forced to create ISAPI extensions or filters. This task was quite daunting and painful for many developers because creating ISAPI extensions and filters required knowledge of C/C++ and knowledge of how to create native Win32 DLLs. Thankfully, in the .NET world, creating these types of low-level applications is really no more difficult than most other applications you might build. This chapter looks at two methods of manipulating how ASP.NET processes HTTP requests, the HttpModule and the HttpHandler. Each method provides a unique level of access to the underlying processing of ASP.NET and can be a powerful tool for creating web applications.

PROCESSING HTTP REQUESTS

Before starting to write handlers or modules, knowing how IIS and ASP.NET normally process incoming HTTP requests and what options you have for plugging custom logic into those requests is helpful. IIS is the basic endpoint for incoming HTTP requests. At a very high level, its job is to listen for and validate incoming HTTP requests. Then it routes them to the appropriate module for processing and returns any results to the original requestor. ASP.NET is one of the modules that IIS may pass requests to for processing. However, exactly how that processing happens and how you can integrate your own logic into the pipeline differs based on the version of IIS you are using.

IIS 6 and ASP.NET

If you are using IIS 6, the HTTP request processing pipeline is fairly black box to a managed code developer. IIS basically treats ASP.NET as one of the modules to which it can pass requests for processing rather than as an integrated part of the IIS request processing pipeline. Figure 30-1 shows the basic request processing pipeline of IIS 6 and ASP.NET.

FIGURE 30-1

image

As you can see, the IIS and ASP.NET request pipelines are very similar and several tasks, such as authentication, are even duplicated between the two pipelines. Furthermore, although you can write handlers and modules using managed code, they are still processed in the isolated context of the ASP.NET process. If you want to integrate deeper into the IIS pipeline you are forced to create modules using native code.

IIS 7 and IIS 8 and ASP.NET

Starting with IIS 7, the request processing pipeline in IIS was completely re-architected using an open and highly extensible module-based system. This change still exists in IIS 8. Instead of IIS seeing ASP.NET as a separate entity, ASP.NET was deeply integrated into the IIS request processing pipeline. As shown in Figure 30-2, the request processing pipeline was streamlined to eliminate duplicate processes and to allow you to integrate managed modules in the pipeline.

FIGURE 30-2

image

Because ASP.NET modules are first-class citizens, you can place them at any point in the pipeline, or even completely replace existing modules with your own custom functionality. Features that previously required you to write custom ISAPI modules in unmanaged code can simply be replaced by managed code modules containing your logic.

ASP.NET Request Processing

Regardless of the IIS version, the basic HTTP request pipeline model has two core mechanisms for handling requests: HttpModules and HttpHandlers. ASP.NET uses those two mechanisms to process incoming ASP.NET requests, generate a response, and return that response to the client. In fact, you are probably already familiar with HttpModules and HttpHandlers — although you might not know it. If you have ever used the inbox caching or the authentication features of ASP.NET, you have used several different HttpModules. Additionally, if you have ever served up an ASP.NET application, even something as simple as a Hello World web page and viewed it in a browser, you have used an HttpHandler. ASP.NET uses handlers to process and render ASPX pages and other file extensions. Modules and handlers allow you to plug into the request-processing pipeline at different points and interact with the actual requests being processed by IIS.

As you can see in both Figures 30-1 and 30-2, ASP.NET passes each incoming request through a layer of preprocessing HttpModules in the pipeline. ASP.NET allows multiple modules to exist in the pipeline for each request. After the incoming request has passed through each module, it is passed to the HttpHandler, which serves the request. Notice that although a single request may pass through many different modules, it can be processed by one handler only. The handler is generally responsible for creating a response to the incoming HTTP request. After the handler has completed execution and generated a response, the response is passed back through a series of post-processing modules, before it is returned to the client.

You should now have a basic understanding of the IIS and ASP.NET request pipeline — and how you can use HttpModules and HttpHandlers to interact with the pipeline. The following sections take an in-depth look at each of these.

HTTPMODULES

HttpModules are simple classes that can plug themselves into the request-processing pipeline. They do this by hooking into a handful of events thrown by the application as it processes the HTTP request. To create an HttpModule, you simply create a class that derives from the System.Web.IHttpModule interface. This interface requires you to implement two methods: Init and Dispose. Listing 30-1 (files App_Code\SimpleModule.cs and App_Code\SimpleModule.vb in the code download for this chapter) shows the class stub created after you implement the IHttpModule interface.

LISTING 30-1: Implementing the IHttpModule interface

VB

Public Class SimpleModule
    Implements IHttpModule
 
    Public Sub Dispose() Implements IHttpModule.Dispose
 
    End Sub
 
    Public Sub Init(context As HttpApplication) Implements IHttpModule.Init
 
    End Sub
End Class

C#

using System;
using System.Web;
 
public class SimpleModule : IHttpModule
{
 
    public void Dispose()
    {
        throw new NotImplementedException();
    }
     
    public void Init(HttpApplication context)
    {
        throw new NotImplementedException();
    }
}

The Init method is the primary method you use to implement HttpModule functionality. Notice that it has a single method parameter — an HttpApplication object named context. This parameter gives you access to the current HttpApplication context, and it is what you use to wire up the different events that fire during the request processing. Table 30-1 shows the events that you can register in the Init method. The Order column indicates the order in which they are fired for the first time. Some events are fired more than once during the processing of a request.

TABLE 30-1

EVENT NAME ORDER DESCRIPTION
AcquireRequestState 9 Raised when ASP.NET run time is ready to acquire the session state of the current HTTP request.
AuthenticateRequest 2 Raised when ASP.NET run time is ready to authenticate the identity of the user.
AuthorizeRequest 4 Raised when ASP.NET run time is ready to authorize the user for the resources that the user is trying to access.
BeginRequest 1 Raised when ASP.NET run time receives a new HTTP request.
Disposed 23 Raised when ASP.NET completes the processing of an HTTP request.
EndRequest 20 Raised just before sending the response content to the client.
Error N/A Raised when an unhandled exception occurs during the processing of the HTTP request.
LogRequest 18 Occurs just before ASP.NET performs any logging for the current request.
PostAcquireRequestState 10 Occurs when the request state (for example, session state) that is associated with the current request has been obtained.
PostAuthenticateRequest 3 Occurs when a security module has established the identity of the user.
PostAuthorizeRequest 5 Occurs when the user for the current request has been authorized.
PostLogRequest 19 Occurs when ASP.NET has completed processing all the event handlers for the LogRequest event.
PostMapRequestHandler 8 Occurs when ASP.NET has mapped the current request to the appropriate event handler.
PostReleaseRequestState 15 Occurs when ASP.NET has completed executing all request event handlers and the request state data has been stored.
PostRequestHandlerExecute 12 Raised just after the HTTP handler finishes execution.
PostResolveRequestCache 7 Occurs when ASP.NET bypasses execution of the current event handler and allows a caching module to serve a request from the cache.
PostUpdateRequestCache 17 Occurs when ASP.NET finishes updating caching modules and storing responses that are used to serve subsequent requests from the cache.
PreRequestHandlerExecute 11 Raised just before ASP.NET begins executing a handler for the HTTP request. After this event, ASP.NET forwards the request to the appropriate HTTP handler.
PreSendRequestContent 14 Raised just before ASP.NET sends the response contents to the client. This event allows you to change the contents before it gets delivered to the client. You can use this event to add the contents, which are common in all pages, to the page output — for example, a common menu, header, or footer.
PreSendRequestHeaders 21 Raised just before ASP.NET sends the HTTP response headers to the client. This event allows you to change the headers before they are delivered to the client. You can use this event to add cookies and custom data into headers.
ReleaseRequestState 13 Occurs after ASP.NET finishes executing all request event handlers. This event causes state modules to save the current state data.
RequestCompleted 22 Occurs when the managed objects that are associated with the request have been released.
ResolveRequestCache 6 Occurs when ASP.NET finishes an authorization event to let the caching modules serve requests from the cache, bypassing execution of the event handler (for example, a page or an XML web service).
UpdateRequestCache 16 Occurs when ASP.NET finishes executing an event handler in order to let caching modules store responses that will be used to serve subsequent requests from the cache.

To see how you can create and use an HttpModule, you can use a simple example involving modifying the HTTP output stream before it is sent to the client. This can be a simple and useful tool if you want to add text to each page served from your website, such as a date/time stamp or the server that processed the request, but do not want to modify each page in your application. To get started creating this HttpModule, create a web application project in Visual Studio and add a class file that inherits from IHttpModule to the App_Code directory. Listing 30-2 (files App_Code\AppendMessage.cs and App_Code\AppendMessage.vb in the code download for this chapter) shows the code for the HttpModule.

LISTING 30-2: Altering the output of an ASP.NET web page

VB

Public Class AppendMessage
    Implements IHttpModule
 
    Dim WithEvents _application As HttpApplication = Nothing
 
    Public Sub Dispose() Implements IHttpModule.Dispose
 
    End Sub
 
    Public Sub Init(context As HttpApplication) Implements IHttpModule.Init
        _application = context
    End Sub
 
    Public Sub context_EndRequest(ByVal sender As Object, ByVal e As EventArgs) _
            Handles _application.EndRequest
 
        Dim message As String = String.Format("processed on {0}",_
           System.DateTime.Now.ToString())
 
        _application.Context.Response.Write(message)
    End Sub
 
End Class

C#

using System;
using System.Web;
 
public class AppendMessage : IHttpModule
{
    private HttpApplication _application = null;
 
    public void Dispose()
    {
            
    }
 
    public void Init(HttpApplication context)
    {
        _application = context;
        context.EndRequest += context_EndRequest;
    }
 
    void context_EndRequest(object sender, EventArgs e)
    {
        string message = string.Format("processed on {0}", 
            System.DateTime.Now.ToString());
        _application.Context.Response.Write(message);
    }
}

You can see that the class stub from Listing 30-1 has been expanded by handling the EndRequest event. This event fires right before the content created by the HttpHandler is sent to the client, and gives you one final opportunity to modify it.

To modify the content of the request, in the EndRequest handler method you simply write your modification to the HttpResponse object’s output stream, which appends the content to the end of the existing content. This sample gets the current date and time and writes it to the output stream. The HTTP request is then sent back to the client.

To use this module, you must let ASP.NET know that you want to include the module in the request-processing pipeline. You do this by modifying the web.config file to contain a reference to the module. Listing 30-3 shows how you can add a <modules> section to your web.config file.

LISTING 30-3: Adding the modules configuration to web.config

<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules>
      <add name="AppendMessage" type="AppendMessage, App_Code" />
    </modules>
  </system.webServer>
</configuration>

The generic format of the <modules> section is

<modules>
    <add name="[modulename]" type="[namespace.classname, assemblyname]" />
</modules>

If you are deploying your application to an IIS 6 server, you must also add the module configuration to the <system.web> configuration section.

<httpModules>
    <add name="AppendMessage" type=" AppendMessage, App_Code"/>
</httpModules>

If you add the <httpModules> section to a web.config file that will potentially be deployed to a server running IIS 7 or IIS 8, you should also add the following validation entry to the <system.webServer> configuration section.

<validation validateIntegratedModeConfiguration="false"/>

Listing 30-4 (file web.config in the code download for this chapter) shows the web.config file after registering the modules in the <system.web> and <system.webServer> configurations sections.

LISTING 30-4: The web.config file after registering the HttpModule

<?xml version="1.0"?>
 
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
 
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <httpModules>
      <add name="AppendMessage" type="AppendMessage, App_Code" />
    </httpModules>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules>
      <add name="AppendMessage" type="AppendMessage, App_Code" />
    </modules>
  </system.webServer>
</configuration>

If you have created your HttpModule in the App_Code directory of an ASP.NET website, you might wonder how you know what the assemblyname value should be, considering ASP.NET now dynamically compiles this code at run time. The solution is to use the text “App_Code” as the assembly name, which tells ASP.NET that your module is located in the dynamically created assembly.

You can also create HttpModules as a separate class library, in which case you simply use the assembly name of the library.

After you have added this section to your web.config file, simply view one of the web pages from your project in the browser. A sample page, Basic.aspx, has been included in the example project. When you view the page in the browser you will see the message added to the very bottom of the page. If you view the source of the page, you will notice the message has been added at the end of the HTML.

Figure 30-3 shows what you should see when you view the page source.

FIGURE 30-3

image

HTTPHANDLERS

HttpHandlers differ from HttpModules, not only because of their positions in the request-processing pipeline (refer to Figures 30-1 and 30-2), but also because they must be mapped to a specific file extension. Handlers are the last stop for incoming HTTP requests and are ultimately the point in the request-processing pipeline that is responsible for serving up the requested content, be it an ASPX page, HTML, plaintext, or an image.

Using HttpHandlers to serve up content you might normally serve using a standard ASP.NET page (such as a dynamic file download request) can be a good idea because it allows you to write a specialized handler that eliminates some of the overhead of a standard ASP.NET handler.

This section demonstrates two ways to create a simple HttpHandler that you can use to serve up images based on dynamic query-string data:

Generic Handlers

Visual Studio provides a standard template for HttpHandlers to help you get started. To add an HttpHandler to your project, you simply select the Generic Handler file type from the Add New Item dialog box. Figure 30-4 shows this dialog box with the file type selected.

FIGURE 30-4

image

You can see that when you add the Generic Handler file to your project, it adds a file with an .ashx extension. The .ashx file extension is the default HttpHandler file extension set up by ASP.NET. Remember that HttpHandlers must be mapped to a unique file extension, so by default ASP.NET uses the .ashx extension. This feature is convenient because, otherwise, you would be responsible for adding the file extension yourself. This task is obviously not always possible, nor is it practical. Using the Custom Handler file type helps you avoid any extra configuration.

Notice the class stub that the file type automatically creates for you. Listing 30-5 shows the class (files txthandler.ashx.cs and txthandler.ashx.vb in the code download for this chapter).

LISTING 30-5: The HttpHandler page template

VB

Imports System.Web
Imports System.Web.Services
 
Public Class imghandler
    Implements System.Web.IHttpHandler
 
    Sub ProcessRequest(ByVal context As HttpContext) Implements _ 
        IHttpHandler.ProcessRequest
 
        context.Response.ContentType = "text/plain"
        context.Response.Write("Hello World!")
 
    End Sub
 
    ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property
 
End Class

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace CS
{
    /// <summary>
    /// Summary description for imghandler
    /// </summary>
    public class imghandler : IHttpHandler
    {
 
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            context.Response.Write("Hello World");
        }
 
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

Notice that the stub implements the IHttpHandler interface, which requires you to implement the ProcessRequest method and IsReusable property.

The handler generated in the template is ready to run right away. Try executing the handler in your browser and see what happens. The interesting thing to note about this handler is that because it changes the content to text/plain, browsers react to the responses from this handler in potentially very different ways depending on a number of factors:

Based on these factors, you might see the text returned in the browser, you might see Notepad open and display the text, or you might receive the Open/Save/Cancel prompt from Internet Explorer. Make sure you understand the potential consequences of changing the ContentType header.

You can continue the example by modifying it to return an actual file. In this case, you use the handler to return an image. An image file, Garden.jpg, has been included in the example download. To do this, you simply modify the code in the ProcessRequest method, as shown in Listing 30-6 (files imghandler.ashx.cs and imghandler.ashx.vb in the code download for this chapter).

LISTING 30-6: Outputting an image from an HttpHandler

VB

Imports System.Web
Imports System.Web.Services
 
Public Class imghandler
    Implements System.Web.IHttpHandler
 
    Sub ProcessRequest(ByVal context As HttpContext) 
        Implements IHttpHandler.ProcessRequest
        'Logic to retrieve the image file
        context.Response.ContentType = "image/jpeg"
        context.Response.WriteFile("Garden.jpg")
    End Sub
 
    ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property
 
End Class

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace CS
{
    /// <summary>
    /// Summary description for imghandler
    /// </summary>
    public class imghandler : IHttpHandler
    {
 
        public void ProcessRequest(HttpContext context)
        {
            //Logic to retrieve the image file
            context.Response.ContentType = "image/jpeg";
            context.Response.WriteFile("Garden.jpg");
        }
 
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

As you can see, you simply change the ContentType to image/jpeg to indicate that you are returning a JPEG image; then you use the WriteFile() method to write an image file to the output stream. Load the handler into a browser, and you see that the handler displays the image. Figure 30-5 shows the resulting web page.

FIGURE 30-5

image

Now, you create a simple web page to display the image handler. Listing 30-7 (file ShowImage.aspx in the code download for this chapter) shows code for the C# version of the web page.

LISTING 30-7: A sample web page using the HttpHandler for the image source

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ShowImage.aspx.cs" 
    Inherits="CS.ShowImage" %>
 
<!DOCTYPE html>
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Generic Image Viewer</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <img src="imghandler.ashx" alt="Dynamic Image" />
    </div>
    </form>
</body>
</html>

Although this is simple, you can enhance the sample by passing query string parameters to your handler and using them to perform additional logic in the handler:

<img src="imghandler.ashx?imageid=123" />

Using the query string data you could, for example, dynamically retrieve an image from a SQL database and return it to the client or perform some type of authentication to ensure the requestor is allowed to access this image.

Mapping a File Extension in IIS

Although using the .ashx file extension is convenient, you might want to create an HTTP handler for a custom file extension or even for a commonly used extension. You can use the code from the image handler to demonstrate this method.

Create a new class in the App_Code directory of your web project. You can simply copy the code from the existing image handler control into this class, as shown in Listing 30-8 (files App_Code\ImgHandler.cs and App_Code\ImgHandler.vb in the code download for this chapter).

LISTING 30-8: The class-based image HttpHandler

VB

Public Class ImgHandler
    Implements System.Web.IHttpHandler
 
    Sub ProcessRequest(ByVal context As HttpContext) Implements _
        IHttpHandler.ProcessRequest
        'Logic to retrieve the image file
        context.Response.ContentType = "image/jpeg"
        context.Response.WriteFile("Garden.jpg")
    End Sub
 
    ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property
End Class

C#

using System.Web;
 
public class ImgHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        //Logic to retrieve the image file
        context.Response.ContentType = "image/jpeg";
        context.Response.WriteFile("Garden.jpg");
    }
 
    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

After adding your class, configure the application to show which file extension this handler serves. You do this by adding a <handlers> section to the web.config file. Listing 30-9 shows the section to add for the image handler.

LISTING 30-9: Adding the handlers’ configuration information to the web.config file

<handlers>
    <add name="ImageHandler" verb="*" path="ImageHandler.img" type="ImgHandler, 
        App_Code" />
</handlers>

In the configuration section, you direct the application to use the ImgHandler class to process incoming requests for ImageHandler.img. You can also specify wildcards for the path. Specifying *.img for the path indicates that you want the application to use the ImgHandler class to process any request with the .img file extension. Specifying * for verb indicates that you want all requests to the application to be processed using the handler.

As with HttpModules, if you are running your web application using IIS 6, then you also must add the <httpHandlers> configuration section to the <system.web> configuration section of your application’s config file. When adding the handler configuration in this section, you should not include the name attribute.

<add verb="*" path="ImageHandler.img" type="ImgHandler, App_Code" />

Load the ImageHandler.img file into a browser and, again, you should see that it serves up the image. Figure 30-6 shows the results. Notice the path in the browser’s address bar leads directly to the ImageHandler.img file.

FIGURE 30-6

image

SUMMARY

This chapter presented a number of ways you can create modules that allow you to interact with the ASP.NET request-processing pipeline. You worked with HttpModules, which give you the power to plug yourself directly into the ASP.NET page-processing pipeline. The events provided to an HttpModule give you great power and flexibility to customize your applications.

You also looked at HttpHandlers. Handlers allow you to skip the ASP.NET page-processing pipeline completely and have complete control over how the framework serves up requested data. You learned how to create your own image handler and then map the handler to any file or file extension you want. Using these features of ASP.NET can help you create features in your application that exercise great control over the standard page processing that ASP.NET uses.