Kyle KingsburyA Sitecore web development blog

Handling Unhandled Exceptions within Sitecore Renderings

InSitecore//5 Min read

Even the best developers cannot stop the inevitable from happening. Whether it be a mistyped variable name within a view, an invalid content type set, or the dreaded null reference exception; errors like these and others creep up from time to time that can in some cases, bring down your Sitecore implementation. Nothing hits a developer harder than knowing their code took down the site, but i'd argue it is much, much worse for the site visitor trying to access that resource and seeing an error message.

While these types of errors cannot be avoided, it's best to have a solution in place to mitigate the risk of having errors in your controllers or views take down a page. Luckily for us, Sitecore has improved the MVC implementation a bit since being first introduced and have exposed a series of classes that wrap the execution of all MVC renderings and provide the ability to add an ErrorStrategy upon errors. This is pretty great and given the way Sitecore typically builds things, this is super flexible and can be modified.

Nothing hits a developer harder than knowing their code took down the site, but i'd argue it is much, much worse for the site visitor trying to access that resource and seeing an error message.

Doing a little digging into the binaries with dotPeek, I notice that the default ErrorStrategy, the PageModeRenderingErrorStrategy class, will display the stack trace within the Experience Editor but allow the error to bubble up to Asp.net as an uncaught exception (if caught by Asp.net, this typically means the yellow screen of death). As I mentioned above, it's far worse to have the site taken down from silly mistakes within your Sitecore rendering, so if the rendering fails to render it should just not render itself.

The 'catch all' error strategy

Let's create a new IRendererErrorStrategy that has a few goals:

  • Be unit-testable :)
  • Catch all exceptions
  • Log the full stacktrace 
  • Inject HTML in place of where the rendering would have rendered on the page, but only for Experience Editor modes
/// <summary>
/// The backup plan for the inevitable
/// </summary>
public class CatchAllRendererErrorStrategy : IRendererErrorStrategy
{
    /// <summary>
    /// Sitecore Logger Implementation
    /// </summary>
    public virtual BaseLog Logger { get { return ServiceLocator.ServiceProvider.GetService<BaseLog>(); } }

    /// <summary>
    /// Are we in Normal Page Mode
    /// </summary>
    public virtual bool IsNormalMode { get { return Sitecore.Context.PageMode.IsNormal; } }

    /// <summary>
    /// Handle any exception directly. If we are in NormalMode, show nothing
    /// but if we are not, let's show an error block to alert the Content Author
    /// </summary>
    public virtual bool HandleError(Renderer renderer, Exception ex, TextWriter writer)
    {
        this.Logger.Error("Rendering Error", ex, typeof(CatchAllRendererErrorStrategy));

        if (!this.IsNormalMode)
        {
            var container = new TagBuilder("div");
            container.Attributes.Add("class", "alert alert-danger");
            container.InnerHtml = "This rendering failed to render. Please contact support to troubleshoot.";

            writer.Write(container.ToString());
        }

        return true;
    }
}

As you can see from the snippet above, the main point of entry for our new ErrorStrategy is the HandleError method which gives us everything we need to handle the error. We first log the exception to Sitecore, move on to checking if we are in Experience Editor and if so writing an error block to the current TextWriter, all while being 100% unit-testable. Also note, always returning true, catches the error entirely so it cannot be bubbled up.

Registering our error strategy

Registering this strategy is fairly straightforward. We need to patch our ErrorStrategy before the default one; registering this feels clunky, so if you can find a better way to register this ErrorStrategy, let me know in the comments! 

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <mvc.renderRendering>
        <processor type="Sitecore.Mvc.Pipelines.Response.RenderRendering.ExecuteRenderer, Sitecore.Mvc">
          <param type="Sitecore.Mvc.Pipelines.Response.RenderRendering.HttpExceptionWrappingRendererErrorStrategy, Sitecore.Mvc" desc="rendererErrorHandler">
            <param type="Sitecore.Mvc.Pipelines.Response.RenderRendering.ChainedRendererErrorStrategy, Sitecore.Mvc" desc="rendererErrorHandler">
              <Handlers hint="list">
                <handler patch:before="handler[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.PageModeRenderingErrorStrategy, Sitecore.Mvc']" type="[Namespace, DLL]"/>
              </Handlers>
            </param>
          </param>
        </processor>
      </mvc.renderRendering>
    </pipelines>
  </sitecore>
</configuration>

Note: Replace the [Namespace, DLL] with your binary information.

To test out the functionality, I created an example rendering on the page (or used an existing one) and threw an exception in the view or controller rendering. And here is an example of our ErrorStrategy in Experience Editor (assumes you are using Bootstrap for styling):

Does your team follow a different approach? Let me know in the comments!