10

I'm using a global action filter to handle and log all exceptions.

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new ElmahHandleErrorAttribute());
        filters.Add(new HandleErrorAttribute());
    }

This is how the global action filter ElmahHandleErrorAttribute is defined - it overrides the OnException method.

public class ElmahHandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
       //Is the exception handled already? context.ExceptionHandled seems to be true here
        if (!context.IsChildAction && (context.HttpContext.IsCustomErrorEnabled))
        {
            //Do other stuff stuff
            //Log to Elmah               
        }
    }
   ...
 }

I don't understand why the value of context.ExceptionHandled is true when the OnException method executes. How is this exception getting handled?

-EDIT- I have a customErrors section in the Web.Config. I have an ErrorController class, and actions called General and Http404.

<customErrors mode ="On" defaultRedirect="Error/General">
      <error statusCode="404" redirect="Error/Http404"/>
  </customErrors>

What I don't understand is, the controller action General is not executed (breakpoint is never hit), but the value of ExceptionContext.ExceptionHandled is set to true when the OnException method of ElmahHandleErrorAttribute starts executing.

escist
  • 763
  • 4
  • 13
  • 35
  • I can't see `context.ExceptionHandled` in your code, where is it? – gdoron May 15 '12 at 09:10
  • @gdoron I removed the check for context.ExceptionHandled. It was earlier included in the statement: `if (!context.IsChildAction && !context.ExceptionHandled && (context.HttpContext.IsCustomErrorEnabled))` – escist May 15 '12 at 09:38
  • Did you override the `OnException` method of the controller? – gdoron May 15 '12 at 09:40
  • @gdoron: No I override the `OnException` method of the MVC `HandleErrorAttribute` filter. – escist May 15 '12 at 10:07

1 Answers1

25

When an exception occurs, the order of the global filters executes in reverse order. This means that HandleErrorAttribute runs first.

You can view the code of HandleErrorAttribute here, but in short, it:

  1. Only executes if ExceptionHandled is false, and custom errors are enabled.
  2. Sets up a redirect to the error view, which by default is called Error.
  3. Sets ExceptionHandled to true.

As it's the first filter, then ExceptionHandled is false when it executes, causing it to set the view to Error and setting ExceptionHandled to true. So, then, when your own filter executes, that is why ExceptionHandled is already set to true. Note that if custom errors were disabled, then ExceptionHandled would still be false, as HandleErrorAttribute wouldn't have done its stuff. In this case, ELMAH will log the error anyway, as it's unhandled (yellow screen of death), so the test in your class is to prevent duplicate logging of the error.

Now, on to your other question about whey the General action isn't executed, the defaultRedirect is only used if the filters don't set some explicit redirect themselves, so it's actually ignored when an exception occurs inside an ActionMethod and you have the global filter HandleErrorAttribute registered. It would however, be called if you entered a URL that didn't exist, i.e. an error that doesn't occur from within an ActionMethod. Also, if you comment out the line to register the HandleErrorAttribute in Global.asax.cs, then you'll always get the General controller action executing.

Richard Fawcett
  • 2,799
  • 1
  • 29
  • 36
  • Thank you for your explanation. It really helped me. I want ElmahHandleErrorAttribute to be the first filter that executes. Would setting the order of this custom filter fix this? As per this comment (http://stackoverflow.com/a/9163926/1242061) I should give a higher Order value to the filter I want to execute first. – escist May 18 '12 at 11:32
  • 1
    Yes, I suspect you could play around with the Order property (but not sure how do do that on a global filter, have only done this when using it as an attribute). Simply reversing the order you add the filters in RegisterGlobalFilters should work ... when an error occurs, the one added last will run first. Not sure why you would want the ELMAH one to run first though, and if you really do, you can't check for ExceptionHandled as it will always be false in that case. – Richard Fawcett May 18 '12 at 12:13
  • I specified the orders when registering the filters. Now my custom filter `ElmahHandleErrorAttribute` is handling the error first. Regarding your question _Not sure why you would want the ELMAH one to run first though_, Do you think this is a bad idea? I'm doing this because I want to display custom error pages and check the error type and display a specific view. – escist May 21 '12 at 06:15
  • Providing that the ELMAH view will always display a view, then yes, I think that's OK. However, if you leave the default `HandleErrorAttribute` to handle the fallback case, if *it* doesn't set a view either, then a YSOD occurs and you'll potentially end up with ELMAH logging the error twice (once in the global filter, and once when the exception isn't marked as handled). If you're setting views in your ELMAH filter, then maybe you should get rid of the `HandleErrorAttribute` altogether? – Richard Fawcett May 21 '12 at 09:19
  • I guess I should get rid of `HandleErrorAttribute`. The custom `ElmahHandleErrorAttribute` will display a view. Since I have defined Application_Error() in `global.asax` to handle 404 and other unhandled exceptions, I don't think a YSOD would be displayed. – escist May 21 '12 at 09:30