Exception Handling in Sitecore MVC

This is 3rd article in series of Framework for Sitecore MVC development. Source code for this article is available on github.

  1. Pluggable Mvc Areas Implementation
  2. Form Post in Siteocre MVC
  3. Exception Handling
  4. Dependency Injection – Container agnostic
  5. Asynchronous Logging – Framework agnostic
  6. Integration with Claims Identity
  7. Glass Mapper Implementation
  8. Unit Testing

This article talks about exception handling for Sitecore Mvc in multi-site implementation and resolves other issues like sending correct http status code and customizing the behavior of MVC renderings.

  1. Common exception handling solution
  2. Multi-site exception handling
  3. Correct HTTP status code and content for error page
  4. Respect CustomError flag set in web.config file
  5. Customize behavior of MVC renderings in case of error

Available solution

John West has talked about different aspect of exception handling in Sitecore MVC, in his 4 blog series.

Handling Exceptions Globally in MVC Solutions Using the Sitecore ASP.NET CMS
Handling Rendering Exceptions in MVC Solutions with the Sitecore ASP.NET CMS
Handling Exceptions Globally in MVC Solutions Using the Sitecore ASP.NET CMS, Revisited
All About Exception Management with the Sitecore ASP.NET CMS

Thumb Rule

A general thumb rule of exception handling is to handle the exception which can be recovered and for all other exceptions, let the default application behavior handle it. Keep the exception handling as simple as possible to avoid failure in failover scenario. Also, with Sitecore, we use nested presentation layer where exception occurred in one of the rendering at Nth level might not be correctly represented once it bubble up to exception handler, so it is desirable to capture all possible information available from exception.

Couple resources for reference as best practice-

Best Practices for Exceptions
Exception Handling Best Practices in .NET

Sitecore MVC Life Cycle

David Morison from Sitecore has a nice diagram with details on Sitecore MVC execution life cycle. This diagram explains how Sitecore runs SitecoreController, various other pipelines to execute MVC controller action & view renderings and collect all generated output to render via response stream. In sort, Sitecore executes all controller/action defined on the page/presentation details one by one and collect the output html and send it to browser for display.

Common exception handling solution
This solution relies on a HTTP Module implementation to handle all application level exceptions and pass it on to a generic factory to determine the exception handler based on current executing site. It could be written inside the Global.asax file itself but my goal is to avoid any code in global.asax file and externalize all code to framework.

 public class ErrorHandlerModule : IHttpModule
    {
        public void Init(HttpApplication application)
        {
            application.Error += new EventHandler(OnError);
        }

        void OnError(object sender, EventArgs e)
        {
            var application = (HttpApplication)sender;
            var error = application.Server.GetLastError();
            var exceptionHandler = ExceptionHandlerFactory.Create();

            var context = new HttpContextWrapper(HttpContext.Current);

            // Custom error flag check 
            if (!context.IsCustomErrorEnabled)
                return;

            exceptionHandler.HandleException(error, context);
        }

        public void Dispose()
        {
        }
    }

I am using Microsoft.Web.Infrastructure library to register the HTTP Module but you can always register through web.config.

 Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(ErrorHandlerModule));

Multi-site exception handling

The other goal of this solution is to utilize and extend the customError section provided by default asp.net implementation to support error handling for multisite where different error pages are required for each web sites.

Currently, this solution has two exception handler, one for web site specific exceptions and relies on custom configuration to identify web site specific error page based on http status code and another one for global exceptions which uses web.config customError section configuration to display the error page. Both the exception handler derive from a base exception handler class with basic implementation.

Site Exception Handler –

public class SiteExceptionHandler : BaseExceptionHandler
    {
        public SiteExceptionHandler(string siteName)
        {
            if (string.IsNullOrWhiteSpace(siteName))
                throw new ArgumentNullException("siteName", "Site name is null.");

            this.siteName = siteName;
        }

        private readonly string siteName = string.Empty;

        public override string GetErrorUrl(int statusCode)
        {
            // Get Url for specific site.
            return GetSiteErrorUrl(statusCode);
        }

        private string GetSiteErrorUrl(int statusCode)
        {
            var statusError = CustomError.Instance.GetSiteErrors(siteName);
            return statusError.FirstOrDefaultStatusUrl(statusCode.ToString(CultureInfo.InvariantCulture));
        }
    }

Global Expcetion Handler –

public class GlobalExceptionHandler : BaseExceptionHandler
    {
        public GlobalExceptionHandler()
            : this(ConfigurationManager.GetSection("system.web/customErrors") as CustomErrorsSection)
        {
        }

        public GlobalExceptionHandler(CustomErrorsSection errorSection)
            : this(SiteErrors.ConvertFrom(errorSection))
        {
        }

        public GlobalExceptionHandler(SiteErrors statusErrors)
        {
            this.statusErrors = statusErrors;
        }

        private readonly SiteErrors statusErrors;

        public override string GetErrorUrl(int statusCode)
        {
            return statusErrors.FirstOrDefaultStatusUrl(statusCode.ToString(CultureInfo.InvariantCulture));
        }
    }

Configuration –

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <sitesErrors>
      <site name="website">
        <errors defaultUrl="/500.aspx">
          <error statusCode="404" url="/404"/>
          <error statusCode="500" url="/500.aspx"/>
        </errors>
      </site>
      <site name="secure">
        <errors defaultUrl="/500.aspx">
          <error statusCode="404" url="/404"/>
          <error statusCode="500" url="/500.aspx"/>
        </errors>
      </site>
    </sitesErrors>
  </sitecore>
</configuration>

 <customErrors mode="RemoteOnly" defaultRedirect="/500.aspx">
      <error statusCode="404" redirect="/404"/>
      <error statusCode="500" redirect="/500.aspx"/>
 </customErrors>
 

Following are the assumption behind the configuration.
Page not found (404) Error – This page can be configured within Sitecore on per site basis.
Unauthorized (401) Error – This page can be configured within Sitecore on per site basis.
Server side (500) Error – As this is a generic error page for any error happening in the application it should be external aspx or html page to avoid any dependency on Sitecore.

Correct HTTP status code and content for error page

Most of the implementation perform Response.Redirect to navigate to error page while handling exception, but this result in sending status code 302 for redirect and 200 for successful rendering of error page itself. In this case browser fails to identify error on server side and treat it as just redirect. To fix this, base exception handler uses Server.TransferRequest to process request with error page, in-order to emit correct status code, I am writing status code directly in ASPX page.

The base class-

public abstract class BaseExceptionHandler
    {
        public abstract string GetErrorUrl(int statusCode);

        public virtual void HandleException(Exception exception, HttpContextBase httpContext)
        {
            if (httpContext == null)
                return;

            if (!httpContext.IsCustomErrorEnabled)
                return;

            if (httpContext.Response.IsRequestBeingRedirected)
                return;

            var httpException = exception as HttpException;
            var statusCode = (int)System.Net.HttpStatusCode.InternalServerError;
            
            if (httpException != null)
                statusCode = httpException.GetHttpCode();

            var redirectUrl = GetErrorUrl(statusCode);

            if (string.IsNullOrWhiteSpace(redirectUrl))
                throw new NullReferenceException("Error Url for status code is null.");

            httpContext.Server.TransferRequest(redirectUrl, true);
        }
    }

MVC Rendering Errors

Exception in Controller/Action execution can be handled by two ways.

  1. with ExecuteRenderer-
    If you are trying to capture any Mvc specific exception then this is the place to add the logic, it capture all Mvc exception including controller not found or Mvc pipeline errors.
    The drawback with this pipeline is that if you want to terminate the execution of the pipeline like any other normal Sitecore pipeline, it doesn’t help much because of pipeline renewing the RenderRenderingArgs argument, and not respecting the AbortPipeline method call which doesn’t terminate the pipeline while executing the MVC request.
  2. with ExceptionProcessor-

    Sitecore adds an ExceptionProcessor pipeline “ShowAspNetErrorMessage” in MVC life cycle to handle MVC related errors. By default this processor doesn’t break the page but displays the error on the page by replacing the output of the rendering, which is good during development but not for live site. You can assume it as .NET YSOD per renderings.

    This processor handles errors during MVC Action method or renderings execution, any failure during Action method execution or view execution will be caught by this processor and handled appropriately. As, this exception is raised on per rendering execution basis, it’s very easy to customize the behavior of individual renderings.

    using System.Web;
    using Framework.Sc.Extensions.ErrorHandler;
    using Sitecore.Mvc.Pipelines.MvcEvents.Exception;
    using System.Web.Mvc;
    
    namespace Framework.Sc.Extensions.Pipelines.MvcEvents
    {
        public class RenderingExceptionProcessor : ExceptionProcessor
        {
            public bool CustomRenderingBehaviourRequired { get; set; }
    
            public override void Process(ExceptionArgs args)
            {
                HandleError(args.ExceptionContext);
            }
    
            protected virtual void HandleError(ExceptionContext exceptionContext)
            {
                var httpContext = exceptionContext.HttpContext;
    
                // Bail if we can't do anything; propogate the error for further processing. 
                if (httpContext == null)
                    return;
                if (!CustomRenderingBehaviourRequired)
                    return;
                if (exceptionContext.ExceptionHandled)
                    return;
                if (httpContext.IsCustomErrorEnabled)
                    exceptionContext.ExceptionHandled = true;
    
                var innerException = exceptionContext.Exception;
                
                // - Todo - custom error handling for rendering.
                // ExceptionHandlerFactory.Create().HandleException(innerException, httpContext);
            }
        }
    }
    

    Current implementation checks for a flag ‘CustomRenderingBehaviourRequeired’ and if it is off, it will let error be handled as global error. For custom behavior, I have a concept demonstrated here – Custom Error Handling in MVC, this solution uses a content service to pull the configured error message for rendering in case of error but a possible drawback is that it may not appear in context of page look and feel.

    An alternative could be to create a data template which can accept the rendering behavior i.e.(Redirect, hide rendering or alternative error rendering) and if it supposed to display error rendering then a field to accept alternative view rendering path. As error rendering is tied to a specific rendering it might result into too many renderings, to avoid that data source of the error rendering should be generic enough to allow specific error messages and control the look and feel of the rendering by configuring CSS classes etc.

    Above behavior can be accomplished by below code.

    1. Hide Rendering- If a rendering fail to execute then just hide the rendering, it can be done via simple code.
      exceptionContext.Result = new EmptyResult();
      
    2. Alternative Rendering- This allows a rendering failure to be handled gracefully and an alternative rendering to be displayed, alternative rendering could be as simple as a Promo which doesn’t have any functionality but just an image.
      exceptionContext.Result = new ViewResult(); // and specify the view to be displayed.
      

Handle Page Not Found (404) Errors

Page not found (404) is handled via two pipeline which register themselves in HttpBeginRequest and HttpRequestProcessed pipeline.

1st pipeline executes after ItemResolver and check if requested url has a corresponding item in sitecore content tree, if it is not then it will load the sitecore item configured for 404 scenario.

2nd pipeline executes after request processing is complete to set the 404 status code.

<httpRequestBegin>
        <processor type="Framework.Sc.Extensions.Pipelines.HttpRequest.PageNotFoundHandler, Framework.Sc.Extensions" patch:before="processor[@type='Sitecore.Pipelines.HttpRequest.LayoutResolver, Sitecore.Kernel']"/>
</httpRequestBegin>
<httpRequestProcessed>
        <processor type="Framework.Sc.Extensions.Pipelines.HttpRequest.SetNotFoundStatusCode, Framework.Sc.Extensions" />
</httpRequestProcessed>

Code for this article is available on GitHub.

About cprakash

A developer, passionate about .Net, Architecture and Security.
This entry was posted in Sitecore, Sitecore MVC and tagged . Bookmark the permalink.

1 Response to Exception Handling in Sitecore MVC

  1. Kevin says:

    Thanks for this post, great summary 🙂 According to this topic I think it’s worth mentioning the Sitecore Error Manager (available from the Marketplace), which takes the default implementation from Sitecore for 404, 500 etc., returns an error page based on a Sitecore item (per site) and set the correct status code.

    Cheers,
    Kevin

Leave a comment