June 24, 2015 by Charlie Turano

Robust MVC Rendering Exception Handler

While working on a recent Sitecore MVC implementation, I started to think about how Sitecore handles errors in the MVC components on a page. In past implementations, I had added a processor to the mvc.exception pipeline to route the user to the error page for the current site. This works reasonably well, but I began to notice a few drawbacks:
  1. There may be components on the page that are considered non-critical, but if they fail, the user can't use the page because they are pushed to the error page.
  2. If the content editor adds a component in the experience editor and it fails, the content editor is pushed to the error page, possibly loosing their work.
  3. If a component on the page is broken, the content editor may not be able to edit the page in the experience editor because they are thrown to the error page. In this case, their only option is to involve a developer to track down the error in the logs and remove the broken component.

Ideal component failure modes

Looking at the list above, I started thinking about how a component on a page could fail in a more user friendly manner. It would be more user friendly if the end user is not presented with a compromised experience if a non-critical component on a page fails. e.g. the user is on the product details page and the cross-sell component fails. The ideally the user would still see the product details but the cross-sell area would be blank. If a content editor tries to edit a page with a broken component, they would see some sort of message indicating that the component is broken and possibly some information about how to resolve the issue. Even if they saw an exception message, they would at least be able to remove the broken component without involving development and directly editing the presentation details.

Page component errors

Once I started thinking about the problem, I realized writing components that fail gracefully and predictably may be more difficult than I previously thought. The two types of components I use most often are ControllerRenderings and ViewRenderings. Trapping errors in the controller portion of a controller rendering is relatively easy. Trapping errors in a view is much more difficult. The view could be wrapped in a try/catch block, but I didn't feel this was an optimal way to solve the problem. Everyone on the dev team must remember to do this in all their views. It makes the views more difficult to work with, and if we change our error handling strategy, it will be a lot of work updating the views.

A better way to handle errors

It would be really nice if we could quietly slip in to the rendering process and automatically wrap each component in a common try/catch block. As Martina pointed out at the 2015 SUGCON in Eindhoven, the answer to every Sitecore question that starts with "In Sitecore can you..." the answer is YES!

How components are rendered

Sitecore renders components on the page using the ControllerRenderer and ViewRenderer classes in the Sitecore.Mvc.Presentation namespace. These classes wrap the Mvc infrastructure that executes the controllers and views. Wrapping these classes would be a great way to trap exceptions on a component and fail gracefully.

The mvc.getRenderer pipeline

Sitecore creates the ControllerRenderer and ViewRenderer classes for the page in the mvc.getRenderer pipeline. We can simply replace the appropriate Sitecore pipeline steps with pipeline steps that return our wrapper rendering classes. The pipeline step for doing this is relatively simple:

public class GetExceptionSafeViewRenderer : GetViewRenderer
{
    protected override Sitecore.Mvc.Presentation.Renderer GetRenderer(Sitecore.Mvc.Presentation.Rendering rendering, GetRendererArgs args)
    {
        string viewPath = this.GetViewPath(rendering, args);
        if (viewPath.IsWhiteSpaceOrNull())
        {
            return null;
        }

        //Return the default view renderer if rendering something from core or the page layout
        if (rendering.RenderingType == "Layout" || 
            rendering.RenderingItem == null || 
            rendering.RenderingItem.Database == null || 
            rendering.RenderingItem.Database.Name == "core")
        {
            return new ViewRenderer
            {
                ViewPath = viewPath,
                Rendering = rendering
            };
        }
        else
        {
            //Return our special renderer
            return new ExceptionSafeViewRenderer
            {
                ViewPath = viewPath,
                Rendering = rendering
            };
        }
    }
}

 

This pipeline step works the same as the Sitecore pipeline step, but it returns our render when rendering the page components. Our view render is also pretty simple:

 

public class ExceptionSafeViewRenderer : ViewRenderer
{
    public override void Render(System.IO.TextWriter writer)
    {
        try
        {
            base.Render(writer);
        }
        catch (Exception ex)
        {
            //Handle exception and prevent it from bubbling up
            Log.Error(string.Format("Failed to process view '{0}'", this.ViewPath), ex, this);

            Exception innerException = ex.InnerException;

            //If pagemode is notmal then render an html comment with some clues to the problem
            if (Sitecore.Context.PageMode.IsNormal)
            {
                writer.WriteLine("<!-- Exception rendering view '{0}': {1} -->",
                        this.ViewPath, 
                        ex.Message);

                while (innerException != null)
                {
                    writer.WriteLine("<!-- Inner Exception: {0} -->", innerException.Message);

                    innerException = innerException.InnerException;
                }
            }
            else
            {
                //In Experience Editor mode, render the exception in a div
                writer.Write("<div class='view-render-exception'>");
                writer.Write("Error rendering view {2}: {0}<br/><pre><code>{1}</code></pre>", 
                        ex.Message, 
                        ex.StackTrace, 
                        this.ViewPath);

                while (innerException != null)
                {
                    writer.Write("<hr/>Inner Exception {0}<br/><pre><code>{1}</code></pre>", 
                        innerException.Message, 
                        innerException.StackTrace);

                    innerException = innerException.InnerException;
                }

                writer.Write("</div>");
            }
        }
    }

 

Most of the functionality in this method is used to handle the exception. In this example, I render an Html comment containing the exception message if the page is in normal mode. If the page is being edited, I render a div with the exception message. I have left out the code for handling controller rendering, because it looks almost exactly the same as the view rendering code. A project with the all the code and patch file will be linked at the bottom of the article.

Going Further

This implementation is a very simple error handler and provides some very basic functionality. If you have components like a check out component, you may want to fail the . Extending the functionality so that a component can declare how it wants to fail should be relatively simple.

Conclusion

This solution was implemented for Sitecore 8 update 3. I have not tested it with other versions of Sitecore, but based on my experience with Sitecore 7.x, it should be compatible with little to no changes. If you wish to download the full project, please click here

Keep in Touch and Stay Informed

Get updates, industry reports, white papers and more Hedgehog love.