Previous Page
Next Page

HttpModules

Overriding Global.asax is a very convenient way to manage data and events within an application. Visual Studio generates a Global.asax for you and even stubs out the more important events for you. However, overriding Global.asax isn't the only way to store state and handle application-wide events. The other way is to write an HTTP Module.

HTTP Modules serve very much the same role ISAPI filters served for classic ASP—as a place to insert functionality into the request processing. HTTP Modules plug into the ASP.NET processing chain to handle application-wide events in the same way that Global.asax handles application-wide events. In fact, many ASP.NET features are implemented through HTTP Modules.

Existing Modules

ASP.NET employs HTTP Modules to enable features such as output caching and session state. To get an idea of what features are implemented via HTTP Modules, take a look at the master configuration file for your machine (that is, go to the Windows directory, look in the Microsoft.NET directory, and drill down to the configuration directory for the most current release). The master Web.Config file mentions several modules in the httpModules section of the configuration, as shown in Listing 17-2. This list does not include entire strong names of the assemblies, but it gives you an idea as to what modules are already part of the ASP.NET pipeline.

Listing 17-2
<httpModules>
 <add name="OutputCache"
  type="System.Web.Caching.OutputCacheModule" />
 <add name="Session"
  type="System.Web.SessionState.SessionStateModule" />
 <add name="WindowsAuthentication"
  type="System.Web.Security.WindowsAuthenticationModule" />
 <add name="FormsAuthentication"
  type="System.Web.Security.FormsAuthenticationModule" />
 <add name="PassportAuthentication"
  type="System.Web.Security.PassportAuthenticationModule" />
<add name="RoleManager"
  type="System.Web.Security.RoleManagerModule" />
<add name="UrlAuthorization"
  type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization"
  type="System.Web.Security.FileAuthorizationModule" />
<add name="AnonymousIdentification"
  type="System.Web.Security.AnonymousIdentificationModule" />
<add name="Profile"
  type="System.Web.Profile.ProfileModule" />
<add name="ErrorHandlerModule"
type="System.Web.Mobile.ErrorHandlerModule " />
</httpModules>

The <httpModules> section mentions the name of a module, followed by a fully specified type that implements the feature. The following features are handled by modules:

Chapter 2 (“ASP.NET Application Fundamentals”) includes a short summary of the ASP.NET pipeline. The Modules fit into the processing chain and take effect prior to being processed by the HttpApplication object. While the features themselves may require extensive code to implement (for example, imagine all the work that went into the session state manager), the basic formula for hooking a module into your application is pretty straightforward. Creating a module involves four steps:

  1. Writing a class implementing IHttpModule

  2. Writing handlers for the events you want handled

  3. Subscribing to the events

  4. Mentioning the module in Web.Config

Implementing a Module

Here's an example illustrating how HTTP Modules work. The earlier example in this chapter demonstrated how to time requests by handling events within Global.asax. The example showed time stamping the beginning of a request, storing the time stamp in the current HttpContext, and examining the time stamp as the request finished.

The following example performs the same functionality. However, the example uses an HTTP Module to handle the events.

A Timing Module
  1. To implement a timing module, open the Web site solution file for this chapter—Use-Application. To work, the module needs to exist in an assembly. It's easiest to write a completely separate assembly for the module. Add a project to the solution by selecting File | Add | New Project from the main menu. Make the project a Class Library and name the project TimingModule.

  2. Visual Studio will add a class to the library named Class1. (The name of the file generated by Visual Studio is Class1.cs and the name of the class generated by Visual Studio is Class1). Change the name of the file to Timer.cs and the name of the class to Timer.

  3. The module as generated by Visual Studio doesn't understand the ASP.NET types. Add a reference to System.Web to make the ASP.NET types available.

  4. Add handlers for the beginning and ending of the request. You may borrow the code from Global.asax if you want to. The signatures for the event's handlers are such that the methods need to return void and accept two arguments: an object and EventArgs.

    using System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    /// <summary>
    /// Summary description for Timer
    /// </summary>
    public class Timer
    {
       public Timer()
       {
       }
    
       public void OnBeginRequest(object o, EventArgs ea)
       {
          HttpApplication httpApp = (HttpApplication)o;
    
          DateTime dateTimeBeginRequest = DateTime.Now;
    
          HttpContext ctx;
          ctx = HttpContext.Current;
          ctx.Items["dateTimeBeginRequest"] = dateTimeBeginRequest;
       }
    
       public void OnEndRequest(object o, EventArgs ea)
       {
          HttpApplication httpApp = (HttpApplication)o;
    
          DateTime dateTimeEndRequest = DateTime.Now;
    
          HttpContext ctx;      
          ctx = HttpContext.Current;
          DateTime dateTimeBeginRequest =
             (DateTime)ctx.Items["dateTimeBeginRequest"];
    
    
          TimeSpan duration = dateTimeEndRequest - dateTimeBeginRequest;
    
    
          ctx.Response.Write("<b> This request took " +
             duration.ToString() + "</b></br>");
       }
    }
  5. Add IHttpModule to the class's inheritance list. Add implementations for the methods Init and Dispose. The job performed by Init is to subscribe to events. The job performed by Dispose is to release any resources used by the module (Dispose doesn't need to do anything in this example).

    using System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    
    /// <summary>
    /// Summary description for Timer
    /// </summary>
    public class Timer :
             IHttpModule
    {
       public Timer()
       {
       }
    
       public void Init(HttpApplication httpApp)
       {
    
          httpApp.BeginRequest +=
             new EventHandler(this.OnBeginRequest);
    
          httpApp.EndRequest +=
             new EventHandler(this.OnEndRequest);
          }
          public void Dispose() { }
    
    // …
    }
  6. By default, the output produced by the compiler is directed to a separate binary and debug directory (that is, it doesn't go in a place where the Web site can use it). To make the assembly available to the Web site, you need to redirect the output of the compiler so it goes in the Web site's bin directory. To add a \Bin folder to the Web site project, right-click on the Web site project in Project Explorer and select Add Folder. Select Bin folder. Then right-click on the Timer project again and select Properties. On the property page, redirect the compiler output to the bin directory for the Web site. You can do this in the Build properties for the TimerModule project. If you created this as an IIS Web site, the bin directory will be directly under the project's IIS virtual directory. The following graphic shows the Visual Studio dialog for changing the target path.

    Graphic
  7. Finally, mention the TimerModule in the Web.Config file. It needs to appear within the <httpModules> section, nested within the <system.web > section, like so:

    <configuration>
     <system.web>
         <httpModules>
          <add name="TimingModule"
               type="Timer, TimingModule" />
         </httpModules>
    </system.web>
    </configuration>

As long as the TimerModule assembly is available to your application (that is, it's in the \bin subdirectory of your virtual directory), it will be linked into the processing chain.

See Active Modules

We saw above that many ASP.NET features are implemented through modules. While you can see the modules listed within the master configuration file, you can also see the list of available modules at runtime. They're available through the current application instance. The following exercise illustrates how to do this.

Listing the Modules
  1. Add a button to the Default.aspx page of the UseApplication solution. This button will list the attached modules, so give it a Text property of Show Modules. Also add a list box to the page that will show the modules.

    Graphic
  2. Double-click on the button to add a handler within the page.

  3. Handle the button event by grabbing the list of modules from the application instance. The list comes back as a collection that you can apply to the list box's DataSource property. Calling DataBind on the ListBox will put the names of all the modules in the ListBox.

    protected void ButtonShowmodules_Click(object sender, EventArgs e)
    {
    
        HttpApplication httpApp = HttpContext.Current.ApplicationInstance;
        HttpModuleCollection httpModuleColl = httpApp.Modules;
    
        Response.Write("<br>");
        String[] rgstrModuleNames;
        rgstrModuleNames = httpModuleColl.AllKeys;
    
        this.ListBox1.DataSource = rgstrModul eNames;
        this.ListBox1.DataBind();
       }

    Running the page and clicking the Show Module button will fill the list box with a list of modules plugged into the application (check out the TimingModule entry in the list).

    Graphic
Storing State in Modules

HTTP Modules are also a very handy place to store global state for your application. The following example shows how to track the average request duration (which requires storing the duration of each request as part of application state).

Tracking Average Request Duration
  1. Before inserting the functionality into the module, let's think a bit about how to use the information about the average request duration. You might use it to profile and to find bottlenecks in your application. While sending the information out to the client browser is always useful, there might be times when you want to use the information programmatically. To retrieve the information from the Module, you'll need to add one or more methods (above and beyond the Init and Dispose methods) to the Timer-Module. The best way to do that is to define an interface that has functions you can use to talk to Module. The following listing defines an interface for retrieving the average request duration. Create a file named ITimerModule.cs and add it to the Timer Module subproject.

    using System;
    public interface ITimerModule
    {
       TimeSpan GetAverageLengthOfRequest();
    }
  2. Implement the ITimer interface within the Timer class. Include a reference to the TimerModule in the Default.aspx page so the page code has access to the interface. Include an ArrayList in the Timer class to hold on to durations of the requests. Store the duration of the request at the end of each request (in the OnEndRequest handler). Use clock ticks as the measurement to make it easier to compute the average duration. Finally, implement GetAverageLengthOfRequest (the method defined by the ITimerModule interface) by adding all the elements in the ArrayList and dividing that number by the size of the Array-List. Create a TimeSpan using the result of the calculation and return that to the client.

    public class Timer : IHttpModule,
                                         ItimerModule{
       public Timer()
       {
       }
    
       protected ArrayList _alRequestDurations =
          new ArrayList();
       public void Init(HttpApplication httpApp)
       {
          httpApp.BeginRequest +=
             new EventHandler(this.OnBeginRequest);
          httpApp.EndRequest +=
             new EventHandler(this.OnEndRequest);
       }
       public void Dispose() { }
    
       public void OnBeginRequest(object o, EventArgs ea)
       {
          HttpApplication httpApp = (HttpApplication)o;
    
          DateTime dateTimeBeginRequest = DateTime.Now;
    
          HttpContext ctx;
          ctx = HttpContext.Current;
          ctx.Items["dateTimeBeginRequest"] = dateTimeBeginRequest;
       }
    
       public void OnEndRequest(object o, EventArgs ea)
       {
          HttpApplication httpApp = (HttpApplication)o;
    
          DateTime dateTimeEndRequest = DateTime.Now;
    
          HttpContext ctx;
          ctx = HttpContext.Current;
          DateTime dateTimeBeginRequest =
             (DateTime)ctx.Items["dateTimeBeginRequest"];
    
          TimeSpan duration =
                   dateTimeEndRequest - dateTimeBeginRequest;
    
          ctx.Response.Write(
                   "<b> From the Timing module; this request took " +
          duration.Duration().ToString() + "</b></br>");
          _alRequestDurations.Add(duration);
       }
       public TimeSpan GetAverageLengthOfRequest()
       {
         long lTicks = 0;
         foreach (TimeSpan timespanDuration in this._alRequestDurations)
         {
              lTicks += timespanDuration.Ticks;
         }    
    
         long lAverageTicks = lTicks / _alRequestDurations.Count;
         TimeSpan timespanAverageDuration = new TimeSpan(lAverageTicks);
         return timespanAverageDuration;
       }
    }
  3. Now add some code in the Default.aspx page to examine the average time taken to process each request. Add a button to fetch the average duration, and a label to display the average duration. Give the button a caption something like “Show Average Duration Of Requests”:

    Graphic
  4. Double-click on the Show Average Duration Of Requests button to add a handler for it. Handle the event by fetching the TimerModule from the collection of Modules. You can fetch it by name because the collection is indexed on the module name (as specified in Web.Config).

       protected void
             ButtonShowAverageDurationOfRequests_Click(
                   object sender,
                   EventArgs e)
       {
          HttpApplication httpApp =
                   HttpContext.Current.ApplicationInstance;
    
          HttpModuleCollection httpModuleColl = httpApp.Modules;
          IHttpModule httpModule =
                   httpModuleColl.Get("TimingModule");
          ITimerModule timerModule =
                   (ITimerModule)httpModule;
    
          TimeSpan timeSpanAverageDurationOfRequest =
             timerModule.GetAverageLengthOfRequest();
          LabelAverageDurationOfRequests.Text =
                   timeSpanAverageDurationOfRequest.ToString();
       }

    The object you get back by accessing the module collection is an HTTP Module. To be able to talk to it using the ITimerModule interface, you need to cast the reference to the Module. Once you do that, you may call GetAverageLengthOfRequest and display it in the label:

    Graphic

To see how the timing module might look in Visual Basic, see Listing 17-3.

Listing 17-3
Imports
System
Imports System.Collections
Imports Timer
Imports System.Web
Public Class TimingModuleVB : Implements IHttpModule

    Protected _alRequestDurations As New ArrayList()

    Public Sub Init(ByVal httpApp As HttpApplication) _
     Implements IHttpModule.Init
        AddHandler httpApp.BeginRequest, _
          New EventHandler(AddressOf OnBeginRequest)

        AddHandler httpApp.EndRequest, _
          New EventHandler(AddressOf OnEndRequest)

    End Sub

    Public Sub Dispose() Implements IHttpModule.Dispose
    End Sub

    Private Sub OnBeginRequest(ByVal src As Object, _
      ByVal e As EventArgs)
        Dim httpApp As HttpApplication
        httpApp = CType(src, HttpApplication)

        Dim dateTimeBeginRequest As DateTime
        dateTimeBeginRequest = DateTime.Now

        Dim ctx As HttpContext
        ctx = HttpContext.Current
        ctx.Items("dateTimeBeginRequest") = dateTimeBeginRequest
    End Sub

    Private Sub OnEndRequest(ByVal src As Object, _
      ByVal e As EventArgs)
        Dim httpApp As HttpApplication
        httpApp = CType(src, HttpApplication)

        Dim dateTimeEndRequest As DateTime
        dateTimeEndRequest = DateTime.Now

        Dim ctx As HttpContext
        ctx = HttpContext.Current

        Dim dateTimeBeginRequest As DateTime

        dateTimeBeginRequest = _
          CType(ctx.Items("dateTimeBeginRequest"), DateTime)

        Dim duration As TimeSpan
        duration = dateTimeEndRequest - dateTimeBeginRequest
        ctx.Response.Write( _
         "<b> From the Timing module; this request took ")
        ctx.Response.Write( _
          duration.Duration().ToString() + "</b></br>")

        _alRequestDurations.Add(duration)
    End Sub

    Function GetAverageLengthOfRequest() As TimeSpan
        Dim lTicks As Long = 0

        Dim timespanDuration As TimeSpan

        For Each timespanDuration In _
          Me._alRequestDurations
            lTicks = lTicks + timespanDuration.Ticks
        Next

        Dim lAverageTicks As Long

        lAverageTicks = lTicks / _alRequestDurations.Count
        Dim timespanAverageDuration As TimeSpan
        timespanAverageDuration = _
          New TimeSpan(lAverageTicks)
        Return timespanAverageDuration

    End Function

End Class

Previous Page
Next Page