Building HTTP Modules


In this section, we’ll create a very simple example, deploy it, and then go back and look more closely at the process, filling in the gaps we left along the way.

A Simple Example

Our straightforward HTTP module example will show how an HTTP module can send a request back to a client before processing has even begun. Open Visual Studio .NET, create a new class library project called SimpleModule, add a reference to the System.Web.dll assembly, and add the following code:

using System; using System.Web; public class SimpleModule : IHttpModule {     public void Init(HttpApplication webApp)     {         webApp.BeginRequest += new EventHandler(webApp_BeginRequest);     }     public void Dispose()     {         }     private void webApp_BeginRequest(object sender, EventArgs e)     {         HttpApplication webApp = (HttpApplication)sender;         HttpContext context = webApp.Context;         webApp.CompleteRequest();         context.Response.StatusCode = 403;         context.Response.StatusDescription =                  "Requests to this application are forbidden";     } }

Save the code as SimpleModule.cs and compile it into an assembly called Notashop.Wscr.C10.SimpleModule.dll. You can do this by setting the assembly name to Notashop.Wscr.C10.SimpleModule on the project’s property pages before building the class. The module is now ready to be deployed. Remember that a module reacts to any request within its Web application, so we’ll create a simple ASP.NET page rather than a Web service, which will more clearly show the module in action.

Create a new Web application project in Visual Studio .NET at http://localhost/wscr/10/testwebapp. Rename webform1.aspx to TestWebPage.aspx and add the following HTML:

<body>     <form  method="post" runat="server">         <p style="color:red">             This is a test page. The handler produced a response here.         </p>     </form> </body>

It doesn’t matter what the page actually does for this example, but it will be handy to have a marker here for two reasons:

  • To prove that this SimpleModule doesn’t allow the test page to execute and print the text.

  • To mark when the ASP.NET handler actually processes the code in the page. HTTP modules let you handle events before and after processing, so a marker is useful for identifying which event occurs when.

Like any other library file, the one containing our module will be accessible to us only if we place it in the Web application’s bin directory or in the global assembly cache (GAC). Copy Notashop.Wscr.C10.SimpleModule.dll into the TestWebApp\bin directory. All that’s left to do is tell the application of our module’s existence. We do this in a .config file.

Open the web.config file for the TestWebApp application, add the following code, and then save it:

<?xml version="1.0" encoding="utf-8" ?> <configuration>   <system.web>     <httpModules>       <add name="SimpleModule"             type="SimpleModule, Notashop.Wscr.c10.SimpleModule" />     </httpModules>   </system.web>  </configuration>

Our module—which should respond to all client requests to the Web application with a 403 Forbidden message—is now in place and is tied into the ASP.NET pipeline. We have only to test it by opening up a browser and trying to access http://localhost/wscr/10/testwebapp/TestWebPage.aspx. Sure enough, the module kicks in and we get a familiar page, as shown in Figure 10-3.

click to expand
Figure 10-3: Our SimpleModule successfully blocks requests with a 403 Forbidden response.

Our first module is written, deployed, and working in less than five pages. Now let’s go back and figure out how it all works.

The IHttpModule Interface

Building a module involves writing a class that inherits and implements the System.Web.IHttpModule interface, plus a number of event handlers:

using System; using System.Web; public class SimpleModule : IHttpModule {        }

The class inherits two methods from IHttpModule that we must implement:

  • Init This method is called when the module is first initialized in the pipeline. It connects the module to events exposed by the HttpApplication object that represents the Web application the request has targeted. The handlers for these events are where the actual work is done by the module. In our SimpleModule example, we use Init to create a handler for the BeginRequest event. In the next section, we’ll look at all the events that can be handled. Init has one parameter—the HttpApplication object—and returns nothing:

    public void Init(HttpApplication webApp) {     webApp.BeginRequest += new EventHandler(webApp_BeginRequest); }

  • Dispose This method is called when the module is removed from the pipeline. It frees any resources, apart from memory, that the module might have been using. It takes no parameters:

    public void Dispose() {     } 

Finally, we need to implement each event handler we set up in Init. Remember that we’re working at the HTTP level, so access to and information about the request and its response must be through the HttpApplication object or the HttpContext object for the request. The first of these is passed as the first parameter in our event handlers, and the second is made available via the HttpApplication.Context property or the HttpContext object’s own static property, Current.

In our example, we use the HttpApplication object’s CompleteRequest method to abort the processing of the request before it has even begun, and we use the HttpContext object to qualify the response to the client with a status code and description, as shown in the following code. One downside of using HTTP modules rather than SOAP extensions is that you must handle errors explicitly. In extensions, an exception generates a SOAP fault automatically.

private void webApp_BeginRequest(object sender, EventArgs e) {     HttpApplication webApp = (HttpApplication)sender;     HttpContext context = webApp.Context;     webApp.CompleteRequest();     context.Response.StatusCode = 403;     context.Response.StatusDescription =          "Requests to this application are forbidden"; }

At the moment, we’re accepting on blind faith that the BeginRequest event will be triggered before processing begins. Next we’ll take a closer look at the events we can hook into and when they occur.

Events Exposed by Modules

The HttpApplication object for your Web application exposes 14 events, each occurring at a significant point in the journey of a request or response message through the ASP.NET pipeline. Six occur before the request is passed to the handler. In chronological order, the 14 events are

  • BeginRequest Indicates that a new request has arrived.

  • AuthenticateRequest Indicates that the request is ready to be authenticated by the Windows authentication module.

  • AuthorizeRequest Indicates that the request is ready to be authorized by the Windows authorization module.

  • ResolveRequestCache Indicates the point at which a module should check whether the response to this request is in the cache. If it is, the response can be drawn from there and sent back to the client, bypassing the need to send the request to the handler.

  • AcquireRequestState Indicates that the handler for the request has been instantiated and has loaded the current state of the application and session before processing the request.

  • PreRequestHandlerExecute Indicates that the handler is about to be passed the request and executed to produce a response.

Each event, with the exception of the security-related events AuthenticateRequest and AuthorizeRequest, has a counterpart that occurs as the request is sent back to the client. In order of occurrence, they are

  • PostRequestHandlerExecute Indicates that the handler has finished processing the request and has produced a response to be sent back to the client.

  • ReleaseRequestState Indicates that the session and application state, as revised by the processing of the request, is ready to be saved.

  • UpdateRequestCache Indicates that the response is ready to be added to the output cache for later use.

  • EndRequest Indicates that the request is ready to be sent to the client. Note that EndRequest is raised as soon as a module calls HttpApplication.CompleteRequest.

  • Disposed Indicates that the response message has left the ASP.NET pipeline and is on its way back to the client.

Last but not least, HttpApplication defines three events that can occur at any time, depending on what the module is doing:

  • PreSendRequestHeaders Indicates that the HTTP headers for the response are about to be sent to the client. It can be triggered by a call to Response.Flush or HttpApplication.CompleteRequest, for example.

  • PreSendRequestContent Indicates that the content of the response is about to be sent to the client. It usually follows PreSendRequestHeaders.

  • Error Indicates that an error has occurred in the module.

Figure 10-4 shows the path that a request and its response take through these events in a module.

click to expand
Figure 10-4: The events exposed by the HttpApplication object, in chronological order

For a demonstration, you can look at a module called EventListModule, which is in the TestWebApp sample application. The code is in EventListModule.cs. The module hooks into every event we named and simply writes to the test page that the event has occurred. For example, here’s the code for the UpdateRequestCache event:

private void webApp_UpdateRequestCache(object sender, EventArgs e) {     HttpContext context = HttpContext.Current;     context.Response.Write("<p>EventListModule: "         + "UpdateRequestCache fired</p>"); }

We simply add the following entry for EventListModule to the web.config file for TestWebApp and comment out the entry we made earlier for SimpleModule:

<add name="EventListModule"       type="EventListModule, Notashop.Wscr.c10.EventListModule" />

If we browse to TestWebPage.aspx, we should see a complete list of events that have occurred during the request for the page, as shown in Figure 10-5.

click to expand
Figure 10-5: EventListModule in action

Adding Asynchronous Events

Thus far, we have considered only the events that occur in synchronous Web applications, but what if the application is asynchronous? The HttpApplication object exposes 10 events that mark the progress of an asynchronous request and its response through the application. They are the equivalent to the 10 synchronous events that are not Disposed, Error, PreSendRequestHeaders, or PreSendRequestContent. As before, we set up our handlers for any asynchronous events we want to trap in Init, but in a slightly different fashion. So far we’ve used the following shortcut notation:

webApp.BeginRequest += new EventHandler(webApp_BeginRequest);

To set up an asynchronous event in a module, we must call the appropriate method exposed by the HttpApplication object. For example, if we want to handle the BeginRequest event asynchronously, we must create a handler for the beginning of the event and a second handler for the end of the event, and we must make the following call in Init:

webApp.AddOnBeginRequestAsync(beginHandler, endHandler);

The method names all take the form AddOnEventNameAsync(beginhandler, endhandler).

Deploying a Module to a Web Application

After you write a module and compile it into a DLL file, deploying it into a Web application is straightforward, as you’ve seen. You follow three simple steps:

  1. Copy the DLL to the Web application’s bin directory or add it to the GAC.

  2. Add an entry for the module to the application’s web.config file.

  3. Restart or rebuild the application.

You don’t need any help for steps 1 and 3, but you might for adding the web.config entry. As you’ll recall from Chapter 5, web.config is where you can add new components to the ASP.NET pipeline for an application and remove any that are present by default because they are set in the global machine.config file. For HTTP modules, we need to add an entry under <configuration>/ <system.web>/<httpModules>; as with handlers, we have three subelements we can use:

  • <add /> This subelement adds a module into the pipeline. It has two attributes. You can use name to provide a friendly name for the module, which you can then use to reference the module in global.asax; you can use type to specify the module’s class name and the assembly that contains it, as in <add name="MyModule" type="ModuleClass, ModuleAssembly" />.

  • <remove /> This subelement removes a module from the application that has been defined globally or in a parent application. For example, machine.config defines seven modules to be included in every application. To remove, say, the UrlAuthorization module, we can add <remove name="UrlAuthorization" /> to our web.config file. The name attribute in this case is the friendly name given to the module when it was added originally.

  • <clear /> This subelement removes every module from the application.

When ASP.NET reads web.config, it performs the instructions it finds there in order, from top to bottom. If we set web.config to read as follows, ASP.NET will remove the seven modules added to our application’s pipeline by default in machine.config. We can then add our own modules.

<?xml version="1.0" encoding="utf-8" ?> <configuration>     <system.web>         <httpModules>             <clear />             <add name="SimpleModule"                   type="SimpleModule, Notashop.Wscr.c10.SimpleModule" />         </httpModules>     </system.web>  </configuration>

If the <clear /> and <add /> elements were switched in this code, ASP.NET would add our module into the pipeline and then remove both it and the seven default modules.

Multiple Modules in One Application

Knowing that an ASP.NET application has by default a number of modules attached to it brings up some interesting questions: How do modules coexist in a pipeline? What happens if two or more handle the same event? Does one have precedence over another? The answers depend on the order in which you add modules to the pipeline. If two modules contain handlers for the same event, the module added first to the pipeline will have its handler called first.

Note that you can also handle all 14 events that are accessible to the module in the global.asax file for your application. Visual Studio .NET autogenerates empty handlers for some of them, but you can access all of them by creating a method with the following signature:

protected void Application_EventName(Object sender, EventArgs e) {        }

In fact, we can go a step further and access any event exposed by a module in global.asax by creating a method with the following signature:

protected void FriendlyModuleName_OnEventName(Object sender, EventArgs e) {        }

Any event handlers defined in global.asax are triggered last, after every corresponding handler defined in an application’s modules. To demonstrate this, the sample code includes a copy of EventListModule called SecondListModule that we can add to our TestWebApp application. Also, a couple of event handlers are defined in the application’s global.asax.cs file that we can uncomment for this demonstration. With EventListModule added before SecondListModule in web.config, EventListModule will always send its event message before SecondListModule, which in turn will always precede messages from handlers in global.asax (as shown in Figure 10-6).

click to expand
Figure 10-6: Events are always handled in order by multiple modules and global.asax.

A Final Module Example

Our final example before we turn our attention to SOAP extensions measures the response time for a request between the point at which ASP.NET receives the request and the point at which it sends the response back to the client. It’s a nice little demonstration of using the Items property of HttpContext to maintain pipeline state information. Items stores information about a specific request as a key/value pair. This information persists for the lifetime of the request, and the response can be accessed by any module or handler that knows to look for it.

You’ll find the code for this module saved as ResponseTimeModule.cs in the sample code. The strategy is simple. We create two event handlers for our module. The first handles the BeginRequest event, saving the current time in the Items request state:

private void webApp_BeginRequest(object sender, EventArgs e) {     HttpApplication webApp = (HttpApplication)sender;     HttpContext context = webApp.Context;     context.Items.Add("Start", System.DateTime.Now); }

The second event handler triggers at the EndRequest event, gets the current time, and finds the difference between it and the time stored previously. The result is the response time of the server, which is added to the page before it is sent back to the client:

private void webApp_EndRequest(object sender, EventArgs e) {     HttpApplication webApp = (HttpApplication)sender;     HttpContext context = webApp.Context;     System.DateTime then = (System.DateTime)context.Items["Start"];     string responseTime = (System.DateTime.Now - then).ToString();     context.Response.Write("<p>It took " + responseTime +                             " to send the request back to you.</p>"); }

Note that we didn’t calculate the response time in the Disposed event. At that point, the response would already be on its way to the client, so we wouldn’t be able to add the time to the page for the client to see. We could use Disposed if we were logging response times on the server.




Programming Microsoft. NET XML Web Services
Programming MicrosoftВ® .NET XML Web Services (Pro-Developer)
ISBN: 0735619123
EAN: 2147483647
Year: 2005
Pages: 172

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net