LibrarySites.Banner

Handling Exceptions Globally in MVC Solutions Using the Sitecore ASP.NET CMS, Revisited

This blog post describes a technique for handling at a global level exceptions solutions that employ MVC with the Sitecore ASP.NET web Content Management System (CMS).

While writing a post about preventing MVC routes from applying to requests, I discovered two things:

  • The MVC exception filter does not appear to apply to exceptions that occur in every aspect of the ASP.NET MVC page lifecycle. Specifically, if an exception occurs while creating a controller, the MVC exception filter does not apply.
  • ASP.NET MVC treats the HTTP 404 condition (page not found, or technically, controller not found) as an exception.

One possible solution is to add exception management logic to the global application (global.asax). I personally try to avoid working with global.asax whenever I can, but I cannot think of a way around it in this case. I would prefer to have my Application_Error() invoke the mvc.exception pipeline, but Application_Error() does not have access to a System.Web.Mvc.ExceptionContext (I could create one, but it would not have a ControllerContext that processors in that pipeline might expect), and anyway not all exceptions trapped at this level necessarily involve MVC.

Here are the contents of my global.asax.cs; hopefully the comments provide enough description.

namespace Sitecore.Sharedsource.Web
{
  using System;
  using System.Collections.Generic;
  using System.Web;
  using System.Web.Configuration;
  
  using SC = Sitecore;
  
  public class Application : SC.Web.Application
  {
    protected void Application_Error(object sender, EventArgs e)
    {
      // if not configured to show friendly errors, 
      // do not handle any exceptions.
      if (SC.Configuration.Settings.CustomErrorsMode == CustomErrorsMode.Off
        || (SC.Configuration.Settings.CustomErrorsMode == CustomErrorsMode.RemoteOnly
          && HttpContext.Current.Request.IsLocal))
      {
        return;
      }
  
      // get and log the exception.
      // Sitecore will also try to log the exception, 
      // which can lead to some duplicate messages in the logs,
      // but this message includes the requested URL,
      // which could be useful for diagnosing issues.
      Exception exception = Server.GetLastError();
      SC.Diagnostics.Log.Error(this + " : Exception processing " + Sitecore.Context.RawUrl, exception, this);
  
      // we're going to handle this exception.
      Response.Clear();
      Server.ClearError();
  
      // treat all exceptions as HTTP 500 by default
      // and redirect/tranfer to a generic error page
      string url = SC.Configuration.Settings.ErrorPage;
  
      // query string parameters to add to the URL
      List<string> list = new List<string>();
  
      // check if it's an HTTP 404.
      // if it is, change the redirect/transfer URL;
      // otherwise, add the error query string parameter
      // used by the generic error page.
      HttpException httpException = exception as HttpException;
  
      if (httpException != null && httpException.GetHttpCode() == 404)
      {
        url = SC.Configuration.Settings.ItemNotFoundUrl;
      }
      else
      {
        list.Add("error");
        list.Add(SC.Globalization.Translate.Text("An unhandled exception occurred."));
      }
  
      // the ExecuteRequest pipeline processor adds these for 404 conditions,
      // and it doesn't hurt to add them for any other conditons.
      list.Add("user");
      list.Add(SC.Context.User != null ? SC.Context.User.Name : String.Empty);
      list.Add("site");
      list.Add(SC.Context.Site != null ? SC.Context.Site.Name : String.Empty);
  
      // if configured to add the URL to the query string on errors, add it.
      if (SC.Configuration.Settings.Authentication.SaveRawUrl)
      {
        list.Add("url");
        list.Add(SC.Context.RawUrl != null ? Sitecore.Context.RawUrl : String.Empty);
      }
  
      // add the query string parameters (this encodes them automatically).
      url = SC.Web.WebUtil.AddQueryString(url, list.ToArray());
  
      // if configured to transfer, transfer, otherwise redirect.
      if (SC.Configuration.Settings.RequestErrors.UseServerSideRedirect)
      {
        HttpContext.Current.Server.Transfer(url);
      }
      else
      {
        SC.Web.WebUtil.Redirect(url, false);
      }
    }
  }
}

To get this working, I updated global.asax in the document root of my Sitecore solution to inherit from this class as follows: 

<%@Application Language='C#' Inherits="Sitecore.Sharedsource.Web.Application" %>

Resources: