ASP.NET

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

Global.asax versus HttpModules

Both the application object expressed through Global.asax and HTTP Modules offer a rendezvous point for your application. You can use both of them to store global state between requests as well as respond to application-wide events. When choosing one over the other, remember that Global.asax really goes with your application. Global.asax is intended to manage state and events specific to your application. HTTP Modules exist as completely separate assemblies. They're not necessarily tied to a particular application, and may even be signed and deployed in the Global Assembly Cache. That makes Modules an ideal vehicle for implementing generic functionality that's useful between different applications.