Reputation: 1708
Problem description:
I am triing to create attributes, that define how to handle exceptions in my ASP.NET MVC 4 controllers, and actions. Specifically they would set the result what to return to the server. I have almost achieved the desired effect, but there is a problem with call orders. Here is an example of what I am doing currently:
[DefaultExceptionHandler]
public abstract class BaseController
{
}
public abstract class AuthorizeController : BaseController
[VeryGoodExceptionHandler]
public class VeryGoodController : AuthorizeController
{
[ViewPageExceptionHandler]
public ActionResult ViewPage()
{
throw new Exception(); //Just for demonstration
return View();
}
public ActionResult ActionWithoutAttribute()
{
return View();
}
}
This is the basic structure of my controllers. The attributes are all descendants of the following class:
public abstract class ExceptionHandlerAttributeBase : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
filterContext.Result = CreateExceptionResult(filterContext.Exception);
}
protected abstract ActionResult CreateExceptionResult(Exception e);
}
CreateExceptionResult can return a JsonResult, a View, a StatusCodeResult, whatever you wish. In my case it always receives a special exception type (Some Enterprise Library magic), but that is not a necessity.
The attribute works very nicely, I put it over something, and the OnException method gets called like it should, putting the proper ActionResult in the filterContext, and sending it back to the client. The problem starts, when I want to override the attribute. It happens, that every OnException methods get called, with the same context. When only the controllers are decorated, everything is (almost) fine, at first, the one put over BaseController gets called, then the one over "VeryGoodController", overriding the result. It would be nice to get rid of the base call, but whatever. In the case of the Action, and the controllers decorated, the calling order happens to be: the action attribute's OnException (setting the desired result), the base controller's OnException, and the child controller's OnException (overriding the desired result). ExceptionContext doesn't have any attributes to use as a flag for further processing (Maybe the Exception attribute could be set to null, but that's not something I'd do).
QUESTION: So the question is how can I get to a) get the OnException method of the Action to be called last, and/or if possible b) deny the less specific methods from being called (in a more efficient way, then just have their result overwritten)?
Some extra info: I have used this answer as a reference. Also, if I register the filter in the way described in this post, and do not add the attributes, the same happens.
Thanks in advance, Robert
Upvotes: 2
Views: 2219
Reputation: 1708
So turns out I have been all wrong to expect something like this can be done.
According to this blog post (and as far as I can tell confirmed by what I managed to test on my own code), the whole exception filter stuff (even if I simply overwrite the Controller.OnException method), is very limited, and only works in certain scenarios. The only (not very appealing) alternatives I can find is to handle the exceptions in some helper class, called from an universal exception helper, in every controller method. The other alternative is to write some extra-disgusting code in the Application_Error block, and handle stuff there.
The blog post mentions a library, which is (in theory) very good, but the 'catches most exceptions' in the description wasn't very appealing.
What I ended up with, is having all my 'expected' exceptions wrapped in some custom exception (this can be brought pretty far, I have some base exception classes, to have some categories on my exceptions, like BusinessRuleViolationException
, or DbException
etc, and the exact exceptions are derived from these.). In my controllers, all action codes are surrounded with a try
block, and have a single catch(Exception e)
block. This block calls the handleexception function of my exceptionhandler utility class, and returns the result.
The exception handler function:
public static ActionResult HandleException(Exception e)
{
try
{
//Calling the EL exception handler block. Configured to rethrow a HandledException.
ExceptionPolicy.HandleException(e, ExceptionPolicyName.StatusCodePolicy.GetCode());
//If no exception happened, that is a problem. Internal Server Error, also logging should be done.
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
//Catch the HandledException. It contains only controlled info on the error, which can be sent to the client. Also, the original exception is already logged by this time.
catch (HandledException ex)
{
return new HttpStatusCodeResult(ex.StatusCode, ex.Message);
}
catch (Exception ex)
{
//If the exception was not a HandledException (very unlikely), that is a problem, it should be logged. Also, Internal Server Error.
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
}
For now this is it, As a last line of defence some code should be put in the Application_Error function, strictly to eliminate any chance of information leak, and logging if anything got there (as critical error presumably). The exception handler function can be parametrised, to be able to provide different results (like StatusCode in an ajax scenario, but error View in a standard scenario), so I believe this solution is pretty flexible, and basically the least ugly of the possibilities. I'm a bit concerned about the amount of throwing exceptions, but meh, this is an MVC 4 webapp, with extensive database communication, this is the least of my performance concerns right now.
Upvotes: 2