Capture HTTP Errors

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

Jeff Prosise

October 30, 2009

9 Min Read
ITPro Today logo

Ask the PRO

LANGUAGES: C#

ASP.NET VERSIONS: 1.0 | 1.1

 

Capture HTTP Errors

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

 

By Jeff Prosise

 

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

 

A: You bet it's possible. The solution is a custom HTTPhandler that examines outgoing HTTP responses and fires an event when it seesan 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 theFramework's IHttpModule interface. Built-in HTTP modules such asSystem.Web.Security.FormsAuthenticationModule and System.Web.SessionState.SessionStateModuleprovide 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 allthe HTTP modules registered in machine.config and web.config and calls theirIHttpModule.Init methods. Inside Init, an HTTP module registers handlers forevents such as HttpApplication.Error and HttpApplication.AuthenticateRequest.In essence, the module sees all requests as they enter and leave theapplication, and it responds to the events that fire as the requests travelthrough ASP.NET's HTTP pipeline. HTTP modules also can fire events of theirown. Thanks to a little magic performed by ASP.NET, you can write handlers forthose events in Global.asax.

 

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

 

using System;

using System.Web;

 

public class HttpErrorModule : IHttpModule

{

    public eventEventHandler HttpError;

 

    public void Init(HttpApplication application)

    {

        application.PreSendRequestHeaders +=

            newEventHandler (OnPreSendRequestHeaders);

    }

 

    voidOnPreSendRequestHeaders (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 statuscode equal to 400 or higher.

 

You must register an HTTP module to use it; register it byadding an element to web.config and reference the modulethere:

 

  

    

              type="HttpErrorModule, HttpErrorModuleLib" />         The registration information includes a logical name forthe module ("HttpError" in this example), the class name (HttpErrorModule), andthe name of the assembly in which the class is implemented(HttpErrorModuleLib). Because ASP.NET doesn't offer a dynamic compilation modelfor 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 binsubdirectory so ASP.NET can find it the next time the application starts up.   The Global.asax file in Figure 2 responds to HttpErrorevents by writing an entry to a log file. (In order for Global.asax to createthe log file, the account that the ASP.NET worker process runs as - typicallyASPNET - must have write/modify access to the folder to which the log file iswritten.) Note the name of the method that handles the event:HttpError_HttpError. The first half of the method name is the logical nameassigned to the module in web.config; the second half is the name of the eventfired by the module. By virtue of the method name, ASP.NET callsHttpError_HttpError automatically when a module named HttpError fires anHttpError event. Thus, you can extend ASP.NET by writing HTTP modules that fireevents, and you can trap those events in Global.asax just as you can trapevents fired by HttpApplication. This little known feature of ASP.NET - itsability 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" %>  void HttpError_HttpError (Object sender, EventArgs e){    HttpApplicationapplication = (HttpApplication) sender;    DateTime time = DateTime.Now;    StreamWriter writer =null;      try {        writer = newStreamWriter             (application.Server.MapPath                 ("HttpErrorLog.txt"), true);        string line =String.Format             ("{0,10:d}    {1,11:T}    {2}    {3}",            time, time,application.Response.StatusCode,            application.Request.Url);        writer.WriteLine(line);    }    finally {        if (writer !=null)            writer.Close();    }}Figure 2. Global.asax's HttpError_HttpError is calledwhen HttpErrorModule fires an HttpError event. This implementation logs theHTTP error in a text file named HttpErrorLog.txt.   Q: I need to create a DataGrid containing acolumn of checkboxes. I figured out how to create the checkboxes with aTemplateColumn, but I don't know how to verify which checkboxes are checkedfollowing a postback. Can you help?   A: Perhaps the .aspx file in Figure 3 can help. Itpopulates a DataGrid with rows from the "Titles" table of SQL Server's Pubsdatabase. Each row contains a checkbox created by a TemplateColumn. After apostback, OnCheckOut reaches into the cells containing the checkboxes and castsControls[1] to type CheckBox. It then reads the CheckBox's checked property todetermine whether the corresponding checkbox is checked. As proof, it displaysa list of the titles the user selected at the bottom of the page (see Figure4).   <%@ 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" />      

              void Page_Load (Object sender, EventArgs e){    if (!IsPostBack) {        //Bind DataGrid to a data source        ...    }}void OnCheckOut (Object sender, EventArgs e){    StringBuilder builder = new StringBuilder (1024);      foreach (DataGridItemitem in MyDataGrid.Items) {        if (item.ItemType== ListItemType.Item ||        item.ItemType ==ListItemType.AlternatingItem) {            CheckBox box =                 (CheckBox)item.Cells[0].Controls[1];            if(box.Checked) {                builder.Append (item.Cells[1].Text);                 builder.Append ("
");            }        }    }    Output.Text =builder.ToString ();  }Figure 3. To read checkbox states from a DataGridcontaining a column of checkboxes, cast Controls[1] in the cells containing thecheckboxes to CheckBox and read the CheckBox's Checked property. This code isonly 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 thatpasses parameters by reference. The method behaves as if I'm passing theparameters by value, even though I've used the ref keyword to mark thempass-by-reference. Are reference parameters not permitted in Web methods?   A: Although reference parameters are permitted in Webmethods, they might not exhibit reference semantics unless you do a littleextra work to help out. Here's a quick summary of the issues involved - andadvice on what to do about them.   Many developers don't realize that ASP.NET Web servicessupport two distinctly different messaging styles: document messaging and RPCmessaging. Document-style messaging is the default, despite the fact that it'snot specifically intended to support request/response message patterns (as isRPC-style messaging). Nevertheless, document-style messaging works well for thevast majority of Web methods.   One drawback to document-style messaging is it doesn'tpreserve 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 boolIsDuplicate (ref Widget w1, ref Widget w2)    {        return (w1 == w2);    }}  public class Widget{    public stringDescription;}Figure 5. The IsDuplicate method in this .asmx filereturns false negatives because it uses document-style messaging, which doesn'tpreserve the identities of types passed by reference in parameter lists.   Its one and only Web method, IsDuplicate, compares the twoinput 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 sameWidget object. In other words, this call to IsDuplicate returns false eventhough 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, whichis enacted by attributing a Web service with the [SoapRpcService] attribute orindividual Web methods with [SoapRpcMethod]. ASP.NET uses the id/href patterndescribed in section 5.4.1 of the SOAP 1.1 specification to maintain theidentities of parameters passed by reference to RPC-style Web methods. Figure 6shows a modified version of the Web service whose IsDuplicate method detectsduplicate 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 boolIsDuplicate (ref Widget w1, ref Widget w2)    {        return (w1 == w2);    }}  public class Widget{    public stringDescription;}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 byreference 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.NETdoesn't preserve the identities of built-in data types such as integers andstrings 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 twocopies of s with no information that indicates they refer to the same string.   One workaround is to wrap the strings in a class or structthat'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 clientStringStruct s = new StringStruct ();s.s = "";ident.TestStringStruct (ref s, ref s);   This solution takes advantage of the fact that ASP.NETpreserves identity information for custom types passed by reference, and, byextension, to types contained within them.   The sample code in thisarticle 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 aquestion for this column? Submit queries to [email protected].      

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like