William Duffy

Glasgow Based C# ASP.NET Web Developer

Using Application_Error in ASP.NET MVC’s global.asax to Handle Errors

ASP.NET MVC’s controllers have great error handling capabilities and can be easily extended to cater for application specific requirements. However, many developer don’t want to manage errors at a controller level and would rather manage 99 percent of errors from a single location. This allows for a single point of error logging, notification and handling.

The Application_Error event within the global.asax is the perfect place for this, but there are a few issues to consider when used in an MVC project.

  1. Server.Transfer is not available to serve an error message as it requires a physical file to serve. Your MVC project more than likely doesn’t have one of theses as it uses routes, controllers and views. We need to find a workaround for this in order to return suitable response headers.
  2. Reponse.Redirect is not suitable as ’500: Internal Server Error’ and ’404: Not Found’ pages should serve a suitable response header, not a 301 redirect.
  3. Response.Clear() should be called to ensure that any content written to the response stream before the error occurred is removed.
  4. Server.ClearError() must be called to stop ASP.NET from serving the yellow screen of death.

With these points in mind the following steps can be coded into the Application_Error event for error handling and logging.

  1. Get the last error raised.
  2. Get the error code to respond with.
  3. Log the error (I’m ignoring 404′s).
  4. Clear the response stream.
  5. Clear the server error.
  6. Render the error handling controller without a redirect.

Here is the Application_Error code…

void Application_Error(object sender, EventArgs e)
{
        var error = Server.GetLastError();
        var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500;
 
        if (code != 404)
        {
                // Generate email with error details and send to administrator
                // I'm using RazorMail which can be downloaded from the Nuget Gallery
                // I also have an extension method on type Exception that creates a string representation
                var email = new RazorMailMessage("Website Error");
                email.To.Add("errors@wduffy.co.uk");
                email.Templates.Add(error.GetAsHtml(new HttpRequestWrapper(Request)));
                Kernel.Get<IRazorMailSender>().Send(email);
        }
 
        Response.Clear();
        Server.ClearError();
 
        string path = Request.Path;
        Context.RewritePath(string.Format("~/Errors/Http{0}", code), false);
        IHttpHandler httpHandler = new MvcHttpHandler();
        httpHandler.ProcessRequest(Context);
        Context.RewritePath(path, false);
}

And here is the ErrorsController code. Notice each action sets the response status error code before rendering the view. Be careful with this controller as any errors will result in an infinite loop between itself and the Application_Error event!

public class ErrorsController : Controller
{
 
        [HttpGet]
        public ActionResult Http404(string source)
        {
                Response.StatusCode = 404;
                return View();
        }
 
        [HttpGet]
        public ActionResult Http500(string source)
        {
                Response.StatusCode = 500;
                return View();
        }
 
}

Tagged as , , , , + Categorized as ASP.NET, C#, MVC

7 Comments

  1. Actually the “pure” ASP.NET MVC way to do this is to implement and register globally (GlobalFilters.Filters.Add) an IExceptionFilter if you want to catch/supporess exceptions (view not found, business layer etc) or an IResultFilter if you want to post-process http codes, etc – it lets either cancel the result or replace it with e.g. new RedirectToAction result or similar.

  2. Ivan that does sound like a much better MVC based approach. I’ll have to try it out.

  3. Hi,

    var error = Server.GetLastError();

    This has a race in it. You are in a multiple user environment and it is global. It can pull the other users then you have the thread count set larger than 1 in the iis config!

  4. @James, I wondered about the race condition myself, but I investigated, and it turns out that the implementation of Server.GetLastError() it calls HttpContext.Error, which provides the per request exception. The only time that this is not the case is when the HttpContext is null, which is fair enough.

  5. Nice solution – even if it is not the pure mvc solution – it did the job for me – thanks.

  6. Other people have commented about the pure MVC way of handling errors. The only thing though is that some errors are not caught by the controller but by the application itself and so in this situation this article is helpful.

  7. Thanks a lot for your solution, it all worked except for that in MVC 4 you have to change the httpHandler.ProcessRequest with a httpContext.Server.TransferRequest.

    Otherwise you get an InvalidOperationException with the following message:
    ‘HttpContext.SetSessionStateBehavior’ can only be invoked before ‘HttpApplication.AcquireRequestState’ event is raised.

    See StackOverflow question for more detail:
    http://stackoverflow.com/questions/10998664/error-when-calling-mvchttphandler-executerequest-from-custom-ihttphandler

    Thanks!

Leave a Reply