Thursday, August 21, 2003

Handling Errors in ASP.NET

 The other day, I was working through some code, adding error handling. We use the Enterprise Instrumentation Framework to do this (oddly only available via MSDN Subscriber download at the Universal level). When I got into the ASP.NET web pages I’d written, I had a bit of a problem. I didn’t really want to add try/catch blocks around every single method in the page’s base class – that would be a little redundant, and would mean that I’d have to change code in a bunch of places if I decided to change the style of errors I was reporting.

This is where it pays to be friends with Fritz Onion. :) I pinged him, and he suggested a clever little hack (in the good sense of that word) to put all my error handling code in one place.

The trick is to re-implement IHttpHandler on the page class itself. Since IHttpHandler::ProcessRequest is the one entry point for all requests into the page – whether that’s a callback handler for an event from the client or the initial Page_Load call – I could simply delegate back to the Page class’s ProcessRequest, but catch any errors that came out of it. One place to put all my high-level error handling. And I could even rethrow the exception if I wanted to still use ASP.NET’s built-in error handling.

It goes something like this:

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public class Default : Page, IHttpHandler
{
  public void ProcessRequest(HttpContext ctx)
  {
    try {
      // Let the Page class itself handle the processing, as usual
      base.ProcessRequest(ctx);
    }
    catch (Exception e) {
      // Log it here – event log, EIF, whatever

      // Use our own error page or simply re-throw
      ctx.Response.Redirect("myerror.aspx");
    }
  }

  public void Page_Load(object o, EventArgs e)
  {
    throw new ApplicationException("Whoops");
  }

  public void Button_OnClick(object o, EventArgs e)
  {
    throw new ApplicationException("Aiieeeee!!!");
  }
}

11 comments:

  1. Cool hack, definitely.

    ReplyDelete
  2. It would be like a sort of "debug" version of RenderChildren method, just to catch exceptions throwed by Web Components without stopping the page rendering. Somthing like:
    foreach( control ... ) {
    try {
    }
    catch {Exception ex) { // ehi, this time the When clause of VB.NET would make the code simpler than C#!
    if (debugEnabled) {
    RaiseEventExceptionInControl( ex );
    }
    else {
    throw;
    }
    }
    }

    Of course the RaiseEventExceptionInControl method and debugEnabled flag has to be defined and handled somewhere.

    ReplyDelete
  3. Woo hoo. This is great.

    ReplyDelete
  4. I'm not sure I understand. If you catch errors at this level, it's already too late to do anything other than log and possibly redirect. You don't know what stage the pipeline is at, so for all you know the controls haven't even been created yet. And it's even more likely that their Render method will never be called, as that happens fairly late in the game.

    So I don't see how you'd be able to actually affect the output of the page, other than to send the user somewhere else. Can you explain if I'm missing something?

    ReplyDelete
  5. As I said, this could be interesting in a debug environment more than in a production one.
    I thought that if you continue to process all web components in a page, you could see some part of the page missing, but you could render other Web Components on the page, and of course catch (and log) more errors in different part of the page.
    The problem borns when, in a developing page, I've seen an error in a Web Component that was at the beginning. That Web Component was not under my control (imagine it stopped work due to an error, and that it was checked out by someone else) and I couldn't test another Web Component that was not related to the first one but, unfortunately, was in the same page.
    So, only for debug purposes, it would be an useful feature... or am I missing something dangerous?

    ReplyDelete
  6. I can't think of anything dangerous, but I'm not sure you'd generally see something that would look like you describe. After all, there are a whole bunch of steps that happen before rendering, so if something went wrong somewhere in one of those, you presumably wouldn't see anything from any of the controls, rather than just seeing the ones that didn't barf.

    Of course, a file being checked out by someone else shouldn't stop you editing it. That's a limitation of the default configuration of SourceSafe, and most other systems don't work like that. Also, "Get Latest, Make Writable" is a reasonable solution when things like that come up. ;)

    ReplyDelete
  7. Works great, seems to cast a wider net than using httpapplication's onerror from my quick tests.

    One thing that's odd is if you enable customErrors in web.config and then use Server.Transfer you'll be catching ThreadAbort every time. What's wierd to me here is that this doesn't happen if customErrors are off.

    So I modified my version to just catch ThreadAbort first in ProcessRequest and do nothing. Which doesn't seem right, but it won't work otherwise. I made a simple sample project, it's pretty easy to reproduce the behavior, i'll post it on my site sometime tonight.

    ReplyDelete
  8. Why not override Page.ProcessRequest (most sites I have worked on have a basepage) and wrap the call to base.ProcessRequest in a try catch? Easier than having to change the config IMO.

    ReplyDelete
  9. Why not just chain to the Page.Error -
    like this this.Error += new EventHandler(PageErrorHandler);
    ?

    ReplyDelete
  10. Why not implement IHttpModule and wire up HttpApplication.Error event in it,



    It will be reusable in any asp.net project and in some case we might need to implement our own IHttpHandler to bypass default asp.net handlers, In those cases IHttpModule is still valid, Because as per my knowledge at a time only one IHttpHandler can be assign to particular request.



    Credit: I learn this technique from http://www.dotnetdevs.com/articles/GlobalErrorHandling.aspx

    ReplyDelete
  11. Sounds like a good idea - another tool for the tool bag.

    ReplyDelete