Capture HTTP Errors

Get up to speed on HTTP modules, checkbox DataGrids, and RPC-style Web methods.

Ask the PRO

LANGUAGES: C#

ASP.NET VERSIONS: 1.0 | 1.1

 

Capture HTTP Errors

Get up to speed on HTTP modules, checkbox DataGrids, and RPC-style Web methods.

 

By Jeff Prosise

 

Q: ASP.NET's HttpApplication class fires an Error event when an unhandled exception occurs, which lets you respond to unhandled exceptions at run time by building an Application_Error method into Global.asax. I'd also like to know when HTTP errors occur so I can trap them in Global.asax, too. Is that possible? ASP.NET doesn't seem to fire any events when it returns an error code in an HTTP response.

 

A: You bet it's possible. The solution is a custom HTTP handler that examines outgoing HTTP responses and fires an event when it sees an HTTP error code being returned. First, I'll give you a bit of background, then I'll show you a sample program that puts words into action.

 

An HTTP module is an object that implements the Framework's IHttpModule interface. Built-in HTTP modules such as System.Web.Security.FormsAuthenticationModule and System.Web.SessionState.SessionStateModule provide key services such as authentication and session management to ASP.NET. Moreover, you can extend ASP.NET by writing HTTP modules of your own.

 

When an application starts up, ASP.NET instantiates all the HTTP modules registered in machine.config and web.config and calls their IHttpModule.Init methods. Inside Init, an HTTP module registers handlers for events such as HttpApplication.Error and HttpApplication.AuthenticateRequest. In essence, the module sees all requests as they enter and leave the application, and it responds to the events that fire as the requests travel through ASP.NET's HTTP pipeline. HTTP modules also can fire events of their own. Thanks to a little magic performed by ASP.NET, you can write handlers for those events in Global.asax.

 

Once you know this, it's no big deal to write an HTTP module that fires events in response to HTTP errors. By hooking the HttpApplication.PreSendRequestHeaders event, an HTTP module can examine the HTTP status code in each HTTP response before the response returns to the client and fire an event if the status code signals an HTTP error. Figure 1 shows what such a module might look like. Its Init method registers a handler for PreSendRequestHeaders events. The handler inspects the status code in the outgoing HTTP response and fires an HttpError event if the status code is 400 or higher. HttpError is a public member of the HTTP module class.

 

using System;

using System.Web;

 

public class HttpErrorModule : IHttpModule

{

    public event EventHandler HttpError;

 

    public void Init (HttpApplication application)

    {

        application.PreSendRequestHeaders +=

            new EventHandler (OnPreSendRequestHeaders);

    }

 

    void OnPreSendRequestHeaders (Object sender,

         EventArgs e)

    {

        HttpApplication application =

             (HttpApplication) sender;

        if (application.Response.StatusCode >= 400 &&

            HttpError != null)

            HttpError (sender, e);

    }

 

    public void Dispose () {}

}

Figure 1. This custom HTTP module, written in C#, fires an HttpError event each time ASP.NET returns in an HTTP response a status code equal to 400 or higher.

 

You must register an HTTP module to use it; register it by adding an element to web.config and reference the module there:

 

  

    

      

        type="HttpErrorModule, HttpErrorModuleLib" />

    

  

 

The registration information includes a logical name for the module ("HttpError" in this example), the class name (HttpErrorModule), and the name of the assembly in which the class is implemented (HttpErrorModuleLib). Because ASP.NET doesn't offer a dynamic compilation model for HTTP modules as it does for HTTP handlers, you must build the assembly - that is, compile the module's source code into a DLL - before registering it. Once it's built, simply place the assembly in the application root's bin subdirectory so ASP.NET can find it the next time the application starts up.

 

The Global.asax file in Figure 2 responds to HttpError events by writing an entry to a log file. (In order for Global.asax to create the log file, the account that the ASP.NET worker process runs as - typically ASPNET - must have write/modify access to the folder to which the log file is written.) Note the name of the method that handles the event: HttpError_HttpError. The first half of the method name is the logical name assigned to the module in web.config; the second half is the name of the event fired by the module. By virtue of the method name, ASP.NET calls HttpError_HttpError automatically when a module named HttpError fires an HttpError event. Thus, you can extend ASP.NET by writing HTTP modules that fire events, and you can trap those events in Global.asax just as you can trap events fired by HttpApplication. This little known feature of ASP.NET - its ability to map events fired by arbitrary HTTP modules to methods in Global.asax - opens the door to a large and diverse assortment of customizations.

 

<%@ Import Namespace="System.IO" %>

 

Figure 2. Global.asax's HttpError_HttpError is called when HttpErrorModule fires an HttpError event. This implementation logs the HTTP error in a text file named HttpErrorLog.txt.

 

Q: I need to create a DataGrid containing a column of checkboxes. I figured out how to create the checkboxes with a TemplateColumn, but I don't know how to verify which checkboxes are checked following a postback. Can you help?

 

A: Perhaps the .aspx file in Figure 3 can help. It populates a DataGrid with rows from the "Titles" table of SQL Server's Pubs database. Each row contains a checkbox created by a TemplateColumn. After a postback, OnCheckOut reaches into the cells containing the checkboxes and casts Controls[1] to type CheckBox. It then reads the CheckBox's checked property to determine whether the corresponding checkbox is checked. As proof, it displays a list of the titles the user selected at the bottom of the page (see Figure 4).

 

<%@ Import Namespace="System.Data.SqlClient" %>

 

  

    

      

        AutoGenerateColumns="false" CellPadding="2"

        BorderWidth="1" BorderColor="lightgray"

        Font-Name="Verdana" Font-Size="8pt"

        GridLines="vertical" Width="100%">

        

          

            ItemStyle-HorizontalAlign="center">

            

              

            

          

          

            DataField="title" />

          

              DataField="price" DataFormatString="{0:c}"

              ItemStyle-HorizontalAlign="right" />

        

        ...

      

      

      

        RunAt="server" />

      

      

    

  

 

Figure 3. To read checkbox states from a DataGrid containing a column of checkboxes, cast Controls[1] in the cells containing the checkboxes to CheckBox and read the CheckBox's Checked property. This code is only an excerpt; you can download the full code.

 


Figure 4. This DataGrid with checkboxes verifies the titles selected.

 

Q: I'm having trouble with a Web method that passes parameters by reference. The method behaves as if I'm passing the parameters by value, even though I've used the ref keyword to mark them pass-by-reference. Are reference parameters not permitted in Web methods?

 

A: Although reference parameters are permitted in Web methods, they might not exhibit reference semantics unless you do a little extra work to help out. Here's a quick summary of the issues involved - and advice on what to do about them.

 

Many developers don't realize that ASP.NET Web services support two distinctly different messaging styles: document messaging and RPC messaging. Document-style messaging is the default, despite the fact that it's not specifically intended to support request/response message patterns (as is RPC-style messaging). Nevertheless, document-style messaging works well for the vast majority of Web methods.

 

One drawback to document-style messaging is it doesn't preserve the identities of objects passed by reference as method parameters. Consider the Web service listed in Figure 5.

 

<%@ WebService Language="C#" Class="IdentiService" %>

 

using System;

using System.Web.Services;

 

[WebService]

public class IdentiService

{

     [WebMethod]

    public bool IsDuplicate (ref Widget w1, ref Widget w2)

    {

        return (w1 == w2);

    }

}

 

public class Widget

{

    public string Description;

}

Figure 5. The IsDuplicate method in this .asmx file returns false negatives because it uses document-style messaging, which doesn't preserve the identities of types passed by reference in parameter lists.

 

Its one and only Web method, IsDuplicate, compares the two input parameters and returns true if they refer to the same Widget object, false if they do not. At least, that's how it's intended to work. In reality, IsDuplicate always returns false, even if the parameters refer to the same Widget object. In other words, this call to IsDuplicate returns false even though it should return true:

 

Widget w = new Widget ();

IdentiService ident = new IdentiService ();

bool IsDuplicate = ident.IsDuplicate

     (ref w, ref w); // Returns false (!)

 

The solution to this problem is RPC-style messaging, which is enacted by attributing a Web service with the [SoapRpcService] attribute or individual Web methods with [SoapRpcMethod]. ASP.NET uses the id/href pattern described in section 5.4.1 of the SOAP 1.1 specification to maintain the identities of parameters passed by reference to RPC-style Web methods. Figure 6 shows a modified version of the Web service whose IsDuplicate method detects duplicate input parameters accurately.

 

<%@ WebService Language="C#" Class="IdentiService" %>

 

using System;

using System.Web.Services;

using System.Web.Services.Protocols;

 

[WebService]

public class IdentiService

{

     [WebMethod]

     [SoapRpcMethod]

    public bool IsDuplicate (ref Widget w1, ref Widget w2)

    {

        return (w1 == w2);

    }

}

 

public class Widget

{

    public string Description;

}

Figure 6. Tagging IsDuplicate with a [SoapRpcMethod] attribute solves the problem by switching to the RPC messaging style.

 

The moral of this story? When you pass parameters by reference to Web methods, you generally should use RPC-style messaging. Otherwise, the results you get might not be the results you expect.

 

As a corollary to this discussion, note that ASP.NET doesn't preserve the identities of built-in data types such as integers and strings when they are passed by reference, even if you use [SoapRpcMethod] or [SoapRpcService]. For example, consider this Web method:

 

[WebMethod]

[SoapRpcMethod]

public void AddStrings (ref string s1, ref string s2)

{

    s1 += "Hello, ";

    s2 += "world";

}

 

Now suppose you call AddStrings this way:

 

string s = "";

ident.AddStrings (ref s, ref s);

 

Following the call, s should equal "Hello, world." Instead, it equals "world," because the Wsdl.exe-generated proxy passes two copies of s with no information that indicates they refer to the same string.

 

One workaround is to wrap the strings in a class or struct that's passed by reference, as demonstrated here:

 

// In the Web service

[WebMethod]

[SoapRpcMethod]

public void AddStrings

   (ref StringStruct s1, ref StringStruct s2)

{

    s1.s += "Hello, ";

    s2.s += "world";

}

...

public class StringStruct

{

    public string s;

}

 

// In the Web service client

StringStruct s = new StringStruct ();

s.s = "";

ident.TestStringStruct (ref s, ref s);

 

This solution takes advantage of the fact that ASP.NET preserves identity information for custom types passed by reference, and, by extension, to types contained within them.

 

The sample code in this article is available for download.

 

Jeff Prosise is the author of several books, including Programming Microsoft .NET (Microsoft Press). He also is a co-founder of Wintellect (http://www.wintellect.com), a software consulting and education firm that specializes in .NET. Got a question for this column? Submit queries to [email protected].

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish