Search This Blog

Thursday, March 24, 2011

What are Http Modules and HTTP Handlers ?

Introduction
All requests to IIS are handled through Internet Server Application Programming Interface (ISAPI) extensions. ASP.NET has its own filter to ensure pages are processed appropriately. By default, the ASP.NET ISAPI filter (aspnet_isapi.dll) only handles ASPX, ASMX, and all other non-display file formats used by .NET and Visual Studio. However, this filter can be registered with other extensions in order to handle requests to those file types, too, but that will be covered later.
Every request flows through a number of HTTP modules, which cover various areas of the application (i.e. authentication and session intofmation). After passing through each module, the request is assigned to a single HTTP handler, which determines how the system will respond to the request. Upon completion of the request handler, the response flows back through the HTTP modules to the user.
HTTP Module
HTTP modules are executed before and after the handler and provide a method for interacting with the request. Custom modules must implement the System.Web.IHttpModule interface. Modules are typically synchronized with events of the System.Web.IHttpModule class (implemented within the Global.asax.cs or .vb file). The following consists of a list of events that should be considered when implementing your module:

  • BeginRequest
  • AuthenticateRequest
  • AuthorizeRequest
  • ResolveRequestCache
  • AcquireRequestState
  • PreRequestHandlerExecute
  • PostRequestHandlerExecute
  • ReleaseRequestState
  • UpdateRequestCache
  • EndRequest
  • PreSendRequestHeaders*
  • PreSendRequestContent*
  • Error*
The events identified by an asterisk (*) can occur at any time within the request; all others are listed in their calling order.
HTTP Handlers
HTTP handlers proces the request and are generally responsible for initiating necessary business logic tied to the request. Custom handlers must implement the System.Web.IHttpHandler interface. Additionally, a handler factory can be created which will analyze a request to determine what HTTP handler is appropriate. Custom handler factories implement the System.Web.IHttpHandlerFactory interface.

When to use Modules and Handlers
With all of the options available in ASP.NET, it is sometimes hard to determine what solution best fits your needs. Of course, it's always best to keep things simple; but, you still need to take evolutionary considerations and experience levels of current and future team members who have a potential of working on teh project into account. Both modules and handlers add a layer of indirection that can be daunting to beginners and/or programmers who are not used to implementing quality designs (read: design patterns).
First, consider what it is that you want to do within your module or handler. Some functions, such as authentication and intstrumentation can be added within modules. Modules should be considered only when there is a need for pass-through and/or intercepting interaction with the request. Alternatively, handlers should be put in place when there is a need to direct functional actions to one or more parts of an application. Probably the most noted use of HTTP handlers is to the FrontController pattern, which allows requests to be refactored and assigned to different components within your application without implementing such changes in every page.
Second, is it worth it? Most applications do not require this level of indirection, which can be hard to understand and/or implement when not documented properly. Some methods, such as the PageController pattern, allow for common functionality to be reused across multiple pages by including this logic in a base System.Web.UI.Page object, and inheriting from this for every web page. When reviewing the PageController implementation, you should know and understand the appropriate use of inheritence. Although certain things can be done this way (i.e. authorization and instrumentation), this is not always the correct means. You should fully understand the pros/cons of utilizing both modules and handlers before deciding on one implementation over the other.
With each of these considerations, and more, the decision to implement modules and/or handlers can be a daunting one. Such decisions should be led by an experienced .NET architect. In the absense of a skilled architect, you will be looking at a lot of leg-work to determine the best solution.
Conclusion
HTTP modules and handlers can be complex. Take the time to fully understand their pros/cons before implementing a solution. I recommend exploiting the experience of software architects whether in your organization or in the community. Whatever you choose, good luck on your ventures. I am finalizing my HTTP handler project, so I should be releasing an article with a sample implementation as well as any recommendations I may have for approaching the job within the next few weeks.

 Implement the Module
  1. Create a new Visual Studio .NET C# Class Library project named MyModule.
  2. Set a reference to the System.Web.dll assembly.
  3. Add the following directive to the class:
    using System.Web;     
  4. Rename the class SyncModule.cs, and then change the class definition to reflect this.
  5. Implement the IHttpModule interface. Your class definition should appear as follows:
    public class SyncModule : IHttpModule     
  6. Decide to which events you will subscribe. The following list outlines the available events from the HttpApplication object to which you can subscribe:
    • AcquireRequestState: Call this event to allow the module to acquire or create the state (for example, session) for the request.
    • AuthenticateRequest: Call this event when a security module needs to authenticate the user before it processes the request.
    • AuthorizeRequest: Call this event by a security module when the request needs to be authorized. Called after authentication.
    • BeginRequest: Call this event to notify a module that new request is beginning.
    • Disposed: Call this event to notify the module that the application is ending for some reason. Allows the module to perform internal cleanup.
    • EndRequest: Call this event to notify the module that the request is ending.
    • Error: Call this event to notify the module of an error that occurs during request processing.
    • PostRequestHandlerExecute: Call this event to notify the module that the handler has finished processing the request.
    • PreRequestHandlerExecute: Call this event to notify the module that the handler for the request is about to be called.
    • PreSendRequestContent: Call this event to notify the module that content is about to be sent to the client.
    • PreSendRequestHeaders: Call this event to notify the module that the HTTP headers are about to be sent to the client.
    • ReleaseRequestState: Call this event to allow the module to release state because the handler has finished processing the request.
    • ResolveRequestCache: Call this event after authentication. Caching modules use this event to determine if the request should be processed by its cache or if a handler should process the request.
    • UpdateRequestCache: Call this event after a response from the handler. Caching modules should update their cache with the response.
    This sample uses the BeginRequest event.
  7. Implement the Init and Dispose methods of the IHttpModule interface as follows:
    public void Init(HttpApplication app)
    {
       app.BeginRequest += new EventHandler(OnBeginRequest);
    }
    
    public void Dispose(){ }     
  8. Create a delegate for an event as follows:
    public delegate void MyEventHandler(Object s, EventArgs e);     
  9. Define a private local variable of the type MyEventHandler to hold a reference to the event:
    private MyEventHandler _eventHandler = null;     
  10. Create an event that hooks up the delegate to the method in the Global.asax file or class that inherits from the HttpApplication object:
    public event MyEventHandler MyEvent
    {
       add { _eventHandler += value; }
       remove { _eventHandler -= value; }
    }     
  11. Create the OnBeginRequest method, which hooks up to the BeginRequest event of HttpApplication:
    public void OnBeginRequest(Object s, EventArgs e)
    {
       HttpApplication app = s as HttpApplication;
       app.Context.Response.Write("Hello from OnBeginRequest in custom module.<br>");
       if(_eventHandler!=null)
          _eventHandler(this, null);
    }     
  12. Compile the project.

Deploy the Module

  1. Create a new directory under C:\Inetpub\Wwwroot named Module.
  2. Create a subdirectory named Bin in the newly created Module directory. The resultant path is C:\Inetpub\Wwwroot\Module\Bin.
  3. Copy MyModule.dll from your project's Bin\Debug directory to the C:\Inetpub\Wwwroot\Module\Bin directory.
  4. Follow these steps to mark the new Module directory as a Web application:
    1. Open Internet Services Manager.
    2. Right-click the Module directory, and then click Properties.
    3. On the Directory tab, click Create.
    4. Click OK to close the Module Properties dialog box.

Configure the System

  1. In the C:\Inetpub\Wwwroot\Module\ directory, create a new file named Web.config.
  2. Paste the following text in Web.config:
    <configuration>
       <system.web>
          <httpModules>
             <add name="MyModule" type="MyModule.SyncModule, MyModule" />
          </httpModules>
       </system.web>
    </configuration>     

Test the Module

  1. In the C:\Inetpub\Wwwroot\Module directory, create a new .aspx file named Test.aspx.
  2. Paste the following text into Test.aspx:
    <%@Page Language="C#"%>
    <% Response.Write("Hello from Test.aspx.<br>"); %>     
  3. In the C:\Inetpub\Wwwroot\Module directory, create a Global.asax file.
  4. Paste the following code into Global.asax:
    <%@ Import Namespace="MyModule" %>
    
    <script language="C#" runat=server >
    protected void MyModule_OnMyEvent(Object src, EventArgs e)
    { 
      Context.Response.Write("Hello from MyModule_OnMyEvent called in Global.asax.<br>");
    }
    </script>     
  5. Request the Test.aspx page. You should see the following lines of text:
    Hello from OnBeginRequest in custom module.
    Hello from MyModule_OnMyEvent called in Global.asax.
    Hello from Test.aspx.     

ASP.NET Request Processing
ASP.NET request processing is based on a pipeline model in which ASP.NET passes http requests to all the modules in the pipeline. Each module receives the http request and has full control over it. The module can play with the request in any way it sees fit. Once the request passes through all of the HTTP modules, it is eventually served by an HTTP handler. The HTTP handler performs some processing on it, and the result again passes through the HTTP modules in the pipeline.
The following figure describes this flow.

Notice that during the processing of an http request, only one HTTP handler will be called, whereas more than one HTTP modules can be called.

Http Handlers
HTTP handlers are the .NET components that implement the System.Web.IHttpHandler interface. Any class that implements the IHttpHandler interface can act as a target for the incoming HTTP requests. HTTP handlers are somewhat similar to ISAPI extensions. One difference between HTTP handlers and ISAPI extensions is that HTTP handlers can be called directly by using their file name in the URL, similar to ISAPI extensions.
HTTP handlers implement the following methods.

Method NameDescription
ProcessRequestThis method is actually the heart of all http handlers. This method is called to process http requests.
IsReusableThis property is called to determine whether this instance of http handler can be reused for fulfilling another requests of the same type. HTTP handlers can return either true or false in order to specify whether they can be reused.

These classes can be mapped to http requests by using the web.config or machine.config file. Once that is done, ASP.NET will instantiate http handler whenever the corresponding request comes in. We will see how to specify all of these details in web.config and/or machine.config files.
ASP.NET also supports the creation of http handlers by means of the IHttpHandlerFactory interface. ASP.NET provides the capability of routing http requests to an object of the class that implements the IHttpHandlerFactory interface. Here, ASP.NET utilizes the Factory design pattern. This pattern provides an interface for creating families of related objects without specifying their concrete classes. In simple terms, you can consider such a class as a factory that creates http handler objects depending on the parameters passed to it. We don't have to specify a particular http handler class to instantiate; http handler factory takes care of it. The benefit of this is if in the future the implementation of the object that implements the IHttpHandler interface changes, the consuming client is not affected as long as the interface remains the same.
These are following methods in IHttpHandlerFactory interface:

Method NameDescription
GetHandlerThis method is responsible for creating the appropriate handler and returns the reference out to the calling code (the ASP.NET runtime). Handler object returned by this method should implement the IHttpHandler interface.
ReleaseHandlerThis method is responsible for releasing the http handler once request processing is complete. The implementation of the factory decides what it should do. Factory implementation can either actually destroy the instance or return it to a pool for future requests.

Registering HTTP Handlers and HTTP Handler Factories in Configuration Files
ASP.NET maintains its configuration information in the following configuration files:

  • machine.config
  • web.config
machine.config file contains the configuration settings that apply to all the Web applications installed on that box.
web.config file is specific to each Web application. Each Web application can have its own web.config file. Any sub directory of a Web application may have its own web.config file; this allows them to override the settings imposed by the parent directories.
We can use <httpHandlers> and <add> nodes for adding HTTP handlers to our Web applications. In fact the handlers are listed with <add> nodes in between <httpHandlers> and </httpHandlers> nodes. Here is a generic example of adding an HTTP handler:

<httpHandlers>
 <add verb="supported http verbs" path="path" type="namespace.classname, assemblyname" />
<httpHandlers>
In the above XML,
  • The verb attribute specifies the HTTP verbs supported by the handler. If the handler supports all of the HTTP verbs, simply use "*", otherwise list the supported verbs in a comma separated list. So if your handler supports only HTTP GET and POST, then verb attribute will be "GET, POST".
  • The path attribute specifies the path or wildcard specification of the files for which this handler will be invoked. For example, if you want your handler to be called only when test.xyz file is requested, then the path attribute will contain "test.xyz"; similarly if you want your handler called for any file having .xyz extension, the path attribute will contain "*.xyz".
  • The type attribute specifies the actual type of the handler or handler factory in the form of a combination of namespace, class name and assembly name. ASP.NET runtime first searches the assembly DLL in the application's bin directory and then searches in the Global Assembly Cache (GAC).

Use of HTTP Handlers by the ASP.NET Runtime
Believe it or not, ASP.NET uses HTTP handlers for implementing a lot of its own functionality. ASP.NET uses handlers for processing .aspx, .asmx, .soap and other ASP.NET files.
The following is the snippet from the machine.config file:

<httpHandlers>
<add verb="*" path="trace.axd" type="System.Web.Handlers.TraceHandler"/>
 
 <add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>
 
 <add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory"/>

 <add verb="*" path="*.config" type="System.Web.HttpForbiddenHandler"/>
 
 <add verb="GET,HEAD" path="*" type="System.Web.StaticFileHandler"/>
 
 . . . . . . 
 . . . . . .
</httpHandlers>
You can see in the above configuration that all the requests for .aspx files are processed by the System.Web.UI.PageHandlerFactory class. Similarly all the requests for .config and other files, which should not be directly accessible to the clients, are handled by the System.Web.HttpForbiddenHandler class. As you might have already guessed, this class simply returns an error to the client stating that these kinds of files are not served.

Implementing HTTP Handlers
Now we will see how to implement an HTTP handler. So what should our new handler do? Well, as I stated above, handlers are mostly used for adding new functionalities to Web servers; therefore, we will create a handler that handles new types of files, files that have a .15seconds extension. Once we implement this handler and register it in the web.config file of our Web application, all requests for .15seconds files will be handled by this new handler.
You might be thinking about the use of such a handler. Well, what if you want to introduce a new kind of server scripting language or dynamic server file such as asp, aspx? You can write your own handler for this. Similarly, what will you do if you want to run Java servlets, JSPs and other server side Java components on IIS? One way of doing this is to install some ISAPI extension like Allaire or Macromedia Jrun. Or you can write your own HTTP handler. Although it is a difficult task for third-party vendors like Allaire and Macromedia, it is a very attractive option because their HTTP handlers will have access to all the new functionalities exposed by the ASP.NET runtime.
Steps involved in implementing our HTTP handler are as follows:
  1. Write a class which implements IHttpHandler interface
  2. Register this handler in web.config or machine.config file.
  3. Map the file extension (.15seconds) to ASP.NET ISAPI extension DLL (aspnet_isapi.dll) in Internet Services Manager.
Step1
Create a new C# Class Library project in Visual Studio.NET and name it "MyHandler". Visual Studio.NET will automatically add a class named "Class1.cs" into the project. Rename it "NewHandler"; open this class in the code window and change the class name and constructor name to "NewHandler". The following is the code for the NewHandler class.

using System;
using System.Web;

namespace MyHandler
{
 /// <summary>
 /// Summary description for NewHandler.
 /// </summary>
 public class NewHandler : IHttpHandler
 {
  public NewHandler()
  {
   //
   // TODO: Add constructor logic here
   //
  }

  #region Implementation of IHttpHandler
  public void ProcessRequest(System.Web.HttpContext context)
  {
   HttpResponse objResponse = context.Response ;
objResponse.Write("<html><body><h1>Hello 15Seconds   Reader ") ;
   objResponse.Write("</body></html>") ;
  }

  public bool IsReusable
  {
   get
   {
    return true;
   }
  }
  #endregion
 }
}
As you can see in the ProcessRequest method, the HTTP handler has access to all ASP.NET intrinsic objects passed to it in its parameter through the System.Web.HttpContext object. Implementing the ProcessRequest method is simply extracting the HttpResponse object from the context object and then sending some HTML out to the client. Similarly, IsReusable returns true to designate that this handler can be reused for processing the other HTTP requests. Let's compile it and place it in the bin directory of the webapp virtual directory.
Step 2
Register this handler by adding the following text in the web.config file:

<httpHandlers>
  <add verb="*" path="*.15seconds" type="MyHandler.NewHandler,MyHandler"/>
</httpHandlers>

Step 3
Since we are creating a handler for handling files of a new extension, we also need to tell IIS about this extension and map it to ASP.NET. If we don't perform this step and try to access the Hello.15seconds file, IIS will simply return the file rather than pass it to ASP.NET runtime. As a consequence, the HTTP handler will not be called. Launch the Internet Services Manager tool, right click on Default Web Site, select Properties, go to Home Directory tab and press Configuration button. This will popup Application Configuration dialog. Click Add button and fill the Executable field with the path to the aspnet_isapi.dll file and fill .15seconds in the Extension field. Leave the other fields as is; the dialog box should look as follows:

Close the Application Configuration and Default Web Site Properties dialog boxes by pressing the OK button.

Session State in HTTP Handlers

Maintaining session state is one of the most common tasks that Web applications perform. HTTP handlers also need to have access to the session state. But session state is not enabled by default for HTTP handlers. In order to read and/or write session data, HTTP handlers are required to implement one of the following interfaces:
  • IRequiresSessionState
  • IReadOnlySessionState.
An HTTP handler should implement the IRequiresSessionState interface when it requires read-write access to the session data. If a handler only needs read access to session data, then it should only implement the IReadOnlySessionState interface.
Both of these interfaces are just marker interfaces and do not contain any methods. So if we want to enable session state for our NewHandler handler, then declare the NewHandler class as followed:

public class NewHandler : IHttpHandler, IRequiresSessionState

No comments:

Post a Comment