views:

625

answers:

2

I've got a catchall in my base controller that handles errors I don't catch. It goes roughly like this:

protected override void OnException(ExceptionContext filterContext)
{
    // Bail if we can't do anything
    if (filterContext == null)
     return;

    // log
    var ex = filterContext.Exception ??
            new Exception("No further information exists.");
    LogException(ex);

    filterContext.ExceptionHandled = true;
    var data = new ErrorPresentation
     {
  ErrorMessage = HttpUtility.HtmlEncode(ex.Message),
  TheException = ex
     };
    filterContext.Result = View("Error", data);
    base.OnException(filterContext);
}

Now this works great if the error happens in the controller. The issue is that if the error is during the aspx page rendering (say, in an HtmlHelper method), the whole error view gets rendered, in place on the page where the error occurred. That means that the entire error page (MasterPage and all) gets rendered within the page that's errored. Not exactly the expected behavior.

I've tried changing View to a RedirectToAction, but that won't work as a route doesn't exist for every controller (Home/Error, Configuration/Error, etc).

How can I get this to work for both errors in the controller AND errors on the page?

+1  A: 

You would need a way to track what state the page is in so that you can differentiate whether you are executing the action -- in which case you want to replace the result -- or executing the result -- in which case you want to discard the contents of the response and execute a new ViewResult for the error. Keeping a flag in the base controller would be one way of handling this. You might also be able to tell based on the exception type. I'm not sure how reliable that would be.

Once you figure out the state you can do:

 var view = View("Error", data );
 if (executingResult)
 {
     filterContext.HttpContext.Response.ClearContent();
     view.ExecuteResult();
 }
 else
 {
      filterContext.Result = view;
      base.OnException( filterContext );
 }

Comment: Generally I don't advocate doing this. I think that your controller action should guarantee to the View that all of the data that it needs is there or your View should be written defensively to ensure that no exceptions (particularly NullReferenceExceptions) don't happen.

tvanfosson
+2  A: 

This is an old question, but for anyone desperately searching, this code works for for me, for exceptions anywhere - in the controller, action or action result, ie the view or any helpers that the view calls. (I got it from Steve Sanderson's book, to give due to where it belongs) :

protected override void OnException(ExceptionContext filterContext)
    {
        if (filterContext.ExceptionHandled)
            return;

        //Let the request know what went wrong
        filterContext.Controller.TempData["Exception"] = filterContext.Exception;

        //redirect to error handler
        filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(
            new { controller = "Exception", action = "HandleError" }));

        // Stop any other exception handlers from running
        filterContext.ExceptionHandled = true;

        // CLear out anything already in the response
        filterContext.HttpContext.Response.Clear();
    }
BillB
Nice, used a piece of it and it worked great.
Andrew