June 24, 2015 | Charlie Turano
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:
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.