Thursday, December 31, 2009

Exceptional Gotchas!

Yes, the pun was intended.

Many ASP.NET developers think that having code in the Application_Error method in Global.asax.cs is adequate for dealing with any unhandled exceptions within their web application. While it usually is adequate--for most basic web applications, there are particular cases where this simply won't catch all errors.

Specifically, if you have code running on the server that is in a thread pool, or you've spawned a separate thread, or you have callback code for a timer, if an unhandled exception occurs, you just might be out of luck.

You can easily test this for yourself by creating a web page with two buttons, one that throws an exception when you click it, and the other that uses a thread from the thread pool and throws an exception within it. Something like this:

public partial class _Default : System.Web.UI.Page
    {
        private void ThrowException()
        {
            throw new ApplicationException("Ouch!");
        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            ThrowException();
        }

        protected void Button2_Click(object sender, EventArgs e)
        {
            System.Threading.ThreadPool.QueueUserWorkItem( new System.Threading.WaitCallback(DoWork));
        }

        private void DoWork(object dummy)
        {
            ThrowException();
        }
    }

In addition to the HttpUnhandledException that gets sent to the Application_Error method, you need to handle the UnhandledException event on the AppDomain.

You can set up a handler in the Application_Start method in Global.asax.cs. But if you go a step further, you can create something a little more reusable. Here's an HttpModule that will demonstrate handling both kinds of web exeptions--those occurring during a normal web request, and those occurring on separate threads. Perhaps it should be part of the Logging Framework.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using BitFactory.Logging;

namespace WebAppA
{
    public class MyExceptionModule : IHttpModule
    {
        private HttpServerUtility Server { get; set; }

        #region IHttpModule Members

        public void Dispose()
        {            
        }

        public void Init(HttpApplication context)
        {
            // deal with any non-main thread exceptions
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

            // deal with any HttpUnhandledExceptions 
            context.Error += new EventHandler(context_Error);

            Server = context.Server;
        }

        void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            // If we are here, the process is about to die
            ConfigLogger.Instance.LogFatal(((Exception)e.ExceptionObject).Message);
        }

        void context_Error(object sender, EventArgs e)
        {
            // The outer exception is usually an HttpUnhandledException, so use the InnerException if it exists.
            // Even more detail can be retrieved by iterateing over each InnerException, if desired.
            Exception ex = Server.GetLastError();
            ex = ex.InnerException ?? ex;
            ConfigLogger.Instance.LogError(ex.Message);
        }

        #endregion
    }
}


Modify the above to suit your needs. In addition, the following would go into your web.config file.

<httpModules>
  <add name="MyExceptionModule" type="WebAppA.MyExceptionModule, WebAppA" />
</httpModules>


You might consider putting something like this into a separate utility assembly that you can reuse in all your web projects.

2 comments:

  1. Copied from Elmah?

    ReplyDelete
  2. Elmah logs unhandled exceptions in ASP.NET apps, and provides some nice ways to views those exceptions. I have a complete logging framework that allows you to log exceptions or any other events from ASP.NET, WinForms, Services, console apps, or anything .NET. The module I provided in this post is a very simple--and very handy--addition to an ASP.NET web application. It's short and sweet, and does just about everything you need (when accompanied by the logging framework) without adding yet another 150K+ DLL to your project.

    ReplyDelete