Now MVC has rescues: Kind Of
I really missed using the [Rescue] attribute from MonoRail in the ASP MVC framework. I missed it so much that I decided to create my own. With the help of Phil Haack’s ConventionController and Frederick Norman’s post on ErrorHandlerAttribute it wasn’t that hard at all. In fact in the end I have something that almost looks like the MonoRail controller syntax. Mind you it is not using any Filters but I really see how easy it would be implement them given the time.
Anyway, the code!
So the first thing I did was create the actual Rescue attribute:
public class RescueAttribute : Attribute { private readonly IList<Type> exceptionTypes = new List<Type>(); private readonly string rescueView; private readonly Action<Type> preRescueAction; public RescueAttribute(string rescueView) : this(rescueView, new Exception().GetType()) { } public RescueAttribute(string rescueView, Type exceptionType) { this.rescueView = rescueView; if (exceptionType != null) exceptionTypes.Add(exceptionType); } public RescueAttribute(string rescueView, Type exceptionType, Action<Type> preRescueAction) { this.rescueView = rescueView; this.preRescueAction = preRescueAction; if (exceptionType != null) exceptionTypes.Add(exceptionType); } public RescueAttribute(string rescueView, params Type[] exceptionType) { this.rescueView = rescueView; if (exceptionType != null) { foreach (Type type in exceptionType) { exceptionTypes.Add(type); } } } public IEnumerable ExceptionTypes { get { return exceptionTypes; } } public string View { get { return rescueView; } } public Action<Type> PreRescueAction { get { return preRescueAction; } } }
Now I had to add a couple of methods to Phil’s ConventionController:
protected override bool OnError(string actionName, MethodInfo methodInfo, Exception exception) { ArrayList attributes = GetRescueHandlerAttribute(methodInfo); foreach (RescueAttribute rescueAttribute in attributes) { foreach (Type exceptionType in rescueAttribute.ExceptionTypes) { if (exceptionType.IsAssignableFrom(exception.InnerException.GetType())) { if(rescueAttribute.PreRescueAction != null) { rescueAttribute.PreRescueAction.Invoke(exceptionType); } if (!string.IsNullOrEmpty(rescueAttribute.View)) RenderView("~/Views/Rescue/" + rescueAttribute.View + ".aspx", exception); return true; } } } return false; } private ArrayList GetRescueHandlerAttribute(ICustomAttributeProvider methodInfo) { ArrayList attributes = new ArrayList(); attributes.AddRange( methodInfo.GetCustomAttributes( typeof (RescueAttribute), false)); attributes.AddRange( GetType().GetCustomAttributes( typeof (RescueAttribute), true)); return attributes; }
The OnError is called when ever there is an Exception within an Action. All I did was simply find all the Rescue Attributes on the controller class and iterate through all the registered exception types. If it finds a registered exception type it looks for a PreAction delegate or lambda and invokes it (still working on this but I thought it was overkill).
Finally it Renders the rescue view in the Rescue folder.
That’s it!
Now my controller looks like this (check out the new hip name for the convention controller:
[Rescue("HelpMe")] public class HomeController : SuperConventionController { public void Index() { RenderView("Index"); } public void About() { RenderView("About"); } public void Error() { throw new NotImplementedException("Did you get this?"); } }
When I type in the following url: http://localhost:52634/Home/Error</a></p>
It immediately redirects me to the Rescue page HelpMe.aspx
I don’t think this code is near production ready but it serves as a quick representation on how quickly and easily you can extend the MVC framework.
Happy coding!