Belangrijke mededeling over deze pagina:

Deze pagina is niet beschikbaar in het Nederlands en daarom wordt de Engelse versie van het artikel getoond. Dit komt vooral voor bij technische artikelen.

In ASP.NET applications you can catch all application exceptions in the Application_Error event handler in the global.asax.
When you use Sitecore MVC, this event is not fired by default.
It took me a while to figure out why this is, so I decided to write a short blog post about it.

What I want to do is:

  • Catch any unhandled application exception.
  • Generate an error report.
  • Display a custom error page with the error report.

My first idea (and what I have done in previous solutions) was to add logic to the Application_Error handler:


  protected void Application_Error(object sender, EventArgs e)
  {
    HttpContext httpContext = HttpContext.Current;

    // Get the exception on which the event has been fired.
    Exception exception = httpContext.Server.GetLastError();
    var errorInfo = new StringBuilder();

    // Generate an error report.
    errorInfo.AppendLine(string.Concat("URL: ", httpContext.Request.Url));
    /* Snipped additional lines of report generation */
    errorInfo.AppendLine(string.Concat("Source: ", exception.Source));
    errorInfo.AppendLine(string.Concat("Message: ", exception.Message));
    errorInfo.AppendLine(string.Concat("Stacktrace: ", exception.StackTrace));
    
    // Store the report in a session variable so we can access it from the custom error page.
    httpContext.Session["Application.ErrorInfo"] = errorInfo.ToString();

    // Return a 500 status code and execute the custom error page.
    httpContext.Server.ClearError();
    httpContext.Response.StatusCode = 500;
    httpContext.Server.Execute("/500.aspx");
  }

The issue with this is that the Application_Error is never fired for Sitecore MVC applications.
That is because, during initialization, Sitecore adds a global MVC filter that implements the OnException event, thereby bypassing the HttpApplication.Error event.

In the <initialize> pipeline the InitializeGlobalFilters processor is added.
That is the processor that adds the PipelineBasedRequestFilter filter to the global filters which implements the OnException() method:


  // Code from Sitecore.Mvc.Pipelines.Loader.InitializeGlobalFilters (Sitcore.Mvc.dll)
  public class InitializeGlobalFilters
  {
    public virtual void Process(PipelineArgs args)
    {
      this.AddGlobalFilters(args);
    }

    protected virtual void AddGlobalFilters(PipelineArgs args)
    {
      GlobalFilters.Filters.Add(this.CreateRequestFilter());
    }
  }

  // Code from Sitecore.Mvc.Filters.PipelineBasedRequestFilter (Sitecore.Mvc.dll)
  public class PipelineBasedRequestFilter : IActionFilter, IResultFilter, IExceptionFilter
  {
    public virtual void OnException(ExceptionContext exceptionContext)
    {
      Assert.ArgumentNotNull((object) exceptionContext, "exceptionContext");
      
      using (TraceBlock.Start("Exception event"))
      {
        PipelineService.Get()
          .RunPipeline<ExceptionArgs>("mvc.exception", new ExceptionArgs(exceptionContext));
      }
    }
  }

So when the OnException() handler is fired, the pipeline is executed.
By default, this pipeline contains just the Sitecore.Mvc.Pipelines.MvcEvents.Exception.ShowAspNetErrorMessage processor which will display the default ASP.NET error page (yellow screen of death).

All we have to do is create our own processor that implements the code that we would have normally put in the Application_Error method.
First of all we change the configuration so that our processor is called instead of Sitecore's default one:


  <pipelines>
    <mvc.exception>
      <processor type="Sitecore.Mvc.Pipelines.MvcEvents.Exception.ShowAspNetErrorMessage, Sitecore.Mvc">
        <patch:attribute name="type">ParTech.Pipelines.HandleMvcException, ParTech</patch:attribute>
      </processor>
    </mvc.exception>
  </pipelines>

And then we create a class for our processor:


    public class HandleMvcException : ExceptionProcessor
    {
        public override void Process(ExceptionArgs args)
        {
            var context = args.ExceptionContext;
            var httpContext = context.HttpContext;
            var exception = context.Exception;

            if (context.ExceptionHandled || httpContext == null || exception == null)
            {
                return;
            }

            // Create a report with exception details.
            string exceptionInfo = this.GetExceptionInfo(httpContext, exception);

            // Store the report in a session variable so we can access it from the custom error page.
            httpContext.Session["Application.ErrorInfo"] = exceptionInfo;

            // Return a 500 status code and execute the custom error page.
            httpContext.Server.ClearError();
            httpContext.Response.StatusCode = 500;
            httpContext.Server.Execute("/500.aspx");
        }

        private string GetExceptionInfo(HttpContextBase httpContext, Exception exception)
        {
            // Generate an error report.
            var errorInfo = new StringBuilder();
            errorInfo.AppendLine(string.Concat("URL: ", httpContext.Request.Url));
      /* Snipped additional lines of report generation */
            errorInfo.AppendLine(string.Concat("Source: ", exception.Source));
            errorInfo.AppendLine(string.Concat("Message: ", exception.Message));
            errorInfo.AppendLine(string.Concat("Stacktrace: ", exception.StackTrace));

            return errorInfo.ToString();
        }
    }

There you have it!
It's a nice way of making sure no application exception has to go unnoticed.

3 Reacties

  • Door Ruud van Falier op 3/10/2014 om 3:32 PM

    Hi Dinkar,

    You are absolutely right about that.
    For those cases you should still implement the Application_Error event.
    It would be an option to make GetExceptionInfo() a public static method and use it in both the Application_Error event and in the HandleMvcException pipeline processor.

    Thank you for pointing this out.

  • Door Dinkar Sharma op 2/14/2014 om 11:47 AM

    But this code does not include errors from controller or connection errors

  • Door Donut op 2/10/2014 om 6:24 PM

    First, thank you. This saved me a lot of time and worked great except for the actual "patch:attribute". I had to just comment their code and implement my class.

Plaats een reactie