views:

69

answers:

1

Will's Error handling post is awesome! I'm not yet using ELMAH, but I'd like to start adding some of my own data to the Exception on a per Controller basis. Right now, I have a base controller overriding OnException(ExceptionContext) as per Will from which all project Controllers inherit.

I think I understand I'll need to customize another OnException override on each Controller, but how do I pass data into that Exception? The data I need may not always be a single text value.

  • Should I throw custom Exceptions?
  • Should I throw generic Exception(string) exceptions and somehow access them within the override?

Edit

This may sound silly, but is there any way to handle an unhandled error and still mine this information? If not, the only solution I can see is creating the custom Exception instance at the beginning of every Controller method for the remote possibility that it will be needed. Is this the only way?

Another Edit

Per Lost In Tangent's post (and part 2), I modified this CustomFactory class, and registered it in Global.asax. The references in his post to base.requestContext weren't valid.

public class CustomFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
    {
        var controller = (Controller)base.GetControllerInstance(requestContext, controllerType);
        controller.ActionInvoker =
            new CustomControllerActionInvoker(new ControllerContext(requestContext, controller));
        return controller; 
    }
}

Then in my CustomControllerActionInvoker class, I override InvokeAction:

public override bool InvokeAction(ControllerContext controllerContext, string actionName)
{
    try
    {
        return base.InvokeAction(controllerContext, actionName);
    }
    catch (Exception)
    {
        throw;
    }
}

I think the catch block is where Brian suggests I create a ViewResult, but how do I create the right kind based on which controller it came from?

+1  A: 

Hey,

If you want to add additional information to the logged message, create a custom exception and add additional properties for information you want to collect. Some loggers log the ToString() method return call, or only log the message, so I don't know what ELMAH does. Most probably do ToString(), so if in the exception you override ToSTring() to add the additional information there, that should be added to the message logged.

Brian
This may sound silly, but is there any way to handle an unhandled error and still mine this information? If not, the only solution I can see is creating the custom Exception instance at the beginning of every Controller method for the remote possibility that it will be needed. Is this the only way?
David
OK, I don't think I may have completely understood the problem. One solution may be to create a custom action invoker that inherits from ControllerActionInvoker. This object is responsible for invoking every action method. Override invoke action, call base.InvokeAction, and then wrap this call in a try/catch block to catch any excpetions thrown. That is one global way.
Brian
This is great :) I didn't go as far as ControllerActionInvoker, but I did override OnException in my base Controller class and make a custom Exception for errors I throw myself. I also used the ExceptionContext.ActionParameters to get the .ToString() methods of the model objects being passed. That's primarily what I was after. I'm trying to figure out how to return the user to the proper View without removing the ELMAH logging ability.
David
Hey, well, in the action invoker, you can then catch the exception, then create a ViewResult of the same view, pass in a working model, and call InvokeActionResult, which invokes the result as if the action worked fine.
Brian
I found this article (http://lostintangent.com/2008/07/03/aspnet-mvc-controlleractioninvoker-part-1/) and the second part, but I'm not sure exactly what you're suggesting go into my CustomControllerActionInvoker.InvokeAction override. I'll update my post with what I have so far. I'm not sure how to "create a ViewResult of the same view, pass in a working model, and call InvokeActionResult"
David
Yes, in your custom InvokeAction, if you call base.InvokeAction within a try{} catch {}, this will catch all errors that occur within a controller. But that action then failed, so you have to redirect them somewhere. The process of redirecting them somewhere involves creating a ViewResult instance and calling InvokeActionResult(); however, I'm not sure how you are going to make the redirection process seamless as if nothing has happened... not saying it's impossible, but it may be difficult in some cases, especially since views typically rely on the model.
Brian
I have some action methods that simply return `Content("Ok")` and others than return `View(customViewModel)`. Can you provide a quick example of each? InvokeAction returns `bool`. Do I somehow call InvokeActionMethod to render the ActionResult and return `true`? Do I somehow use CreateActionResult? Any recommendation on readings to better understand this process?
David
I don't know of any readings that better understand the process... I read the source code. That's what I'm trying to say, I don't see how you can just redirect the user back to the page as if nothing happened, because of the complexity of the different responses, and because of the model each view needs. But again, I'm not 100% sure. Sorry I can't be of more help...
Brian