LibrarySites.Banner

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

This blog post describes some options for handling exceptions in MVC solutions using the Sitecore ASP.NET web Content Management System (CMS), and provides an example of a global exception handler.

Updated 18.September.2012: See the "Revisited" link in the resources listead at the bottom of this page for information about important exception conditions that the solution described in this blog post does not address. 

You can use a variety of techniques to handle different types of exceptions that can occur when processing requests with MVC using the Sitecore ASP.NET CMS:

  • Implement try...catch...finally blocks in your code. Use this approach to trap exceptions that you expect and from which you can recover as close as possible to their occurrence. Unfortunately, adding try...catch...finally blocks to Razor views does not make them easier to read.
  • Override the classes that inherit from the Sitecore.Mvc.Presentation.Renderer class and the mvc.getRenderer processors that retrieve objects of those types to handle exceptions in specific types of renderings.
  • Override the mvc.renderRendering pipeline to handle exceptions generated by any type of presentation component.
  • Implement an mvc.exception pipeline processor to handle any exception that you do not trap at a lower level. Sitecore adds an implementation of the IExceptionFilter interface to System.Web.Mvc.GlobalFilters to invoke the mvc.exception pipeline whenever an MVC component does not trap an exception. This pipeline contains no processors by default.

Note that I have ordered these by scope, starting with the deepest scope and proceeding to the most general. The first approach listed here is out of scope for this blog post. I intend to write additional posts that address the second and third approaches. This blog post provides a solution using the fourth approach. Update 13.September.2012: the link about XSL exceptions at the bottom of this page describes a solution that uses the second approach. Update 14.September.2012: the link about rendering exceptions at the bottom of this page describes a solution that uses the third approach.

If you do nothing, and an MVC presentation component generates an exception, the Sitecore CMS 6.6 technology preview writes the exception message and stack trace to the output streamregardless of the value of the mode attribute of the /configuration/system.web/customErrors element in the /web.config file. Because exposing exception details to an outside world presents a security risk, I reported this to Sitecore customer service as case #370613; engineering may address this before releasing Sitecore CMS 6.6 as a product. Exception details do not appear in the Sitecore log; I did not investigate whether Sitecore clears the error in this case, if the exception handler registered by Sitecore’s HTTP module does not apply to MVC requests, or if something else prevents this. If engineering does not address this issue before you need to use Sitecore CMS 6.6, you can implement an mvc.exception pipeline processor as described in this blog post.

To implement a processor in the mvc.exception pipeline, create a class such as the following:

namespace Sitecore.Sharedsource.Mvc.Pipelines.MvcEvents.Exception
{
  using System.Web;
  using System.Web.Configuration;
  
  using SC = Sitecore;
  
  public class GenericExceptionHandler :
    SC.Mvc.Pipelines.MvcEvents.Exception.ExceptionProcessor
  {
    public override void Process(
      SC.Mvc.Pipelines.MvcEvents.Exception.ExceptionArgs args)
    {
      SC.Diagnostics.Log.Error(
        "MVC exception processing " + SC.Context.RawUrl,
        args.ExceptionContext.Exception,
        this);
      CustomErrorsMode mode = SC.Configuration.Settings.CustomErrorsMode;
  
      if (mode == CustomErrorsMode.Off 
        // to show details to Sitecore admins: || SC.Context.User.IsAdministrator 
        // to show in the Page Editor: || SC.Context.PageMode.IsPageEditor
        // to show in Preview: || SC.Context.PageMode.IsPreview
        // to show in the debugger: || SC.Context.PageMode.IsDebugging
        || (mode == CustomErrorsMode.RemoteOnly && HttpContext.Current.Request.IsLocal))
      {
        return;
      }
  
      SC.Web.WebUtil.RedirectToErrorPage(
        SC.Globalization.Translate.Text("An error occurred."));
      args.ExceptionContext.ExceptionHandled = true;
    }
  }
}

With this processor enabled, if an exception occurs while processing a request with MVC, Sitecore logs the exception and unless the value of the mode attribute is Off or is RemoteOnly and the request originated from the local machine (or you enable any of the additional criteria), redirects the browser to the URL specified by the ErrorPage setting in the Web.config file, appending a query string parameter named error containing the error message.

You can use a Web.config include file such as the following (/App_Config/Include/Sitecore.Sharedsource.Mvc.Exception.config in my case) to add this processor to the pipeline:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <mvc.exception>
        <processor type="Sitecore.Sharedsource.Mvc.Pipelines.MvcEvents.Exception.GenericExceptionHandler, Sitecore.Sharedsource.Mvc"/>
      </mvc.exception>
    </pipelines>
  </sitecore>
</configuration>

So far, I have tested this under only one case: a syntax error in an XSL rendering. Numerous additional exception contexts are possible and you should test each, for example if a controller or view throws an exception. Some of the settings in the /App_Config/Include/Sitecore.Mvc.config Web.config include file indicate additional potential exception conditions, such as if you try to register a route that matches the Mvc.IllegalRoutes setting, if the request attempts to activate a controller (Mvc.DetailedErrorOnMissingController) or an action (Mvc.DetailedErrorOnMissingAction) that does not exist.

Resources: