1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web.Mvc;
5:
6: namespace SquaredRoot.Mvc.Filters.ErrorHandling
7: {
8: public abstract class RedirectOnErrorAttribute : ActionFilterAttribute
9: {
10:
11: public Type Type { get; set; }
12:
13: public override void OnActionExecuted( FilterExecutedContext filterContext )
14: {
15:
16: //### check for errors
17: if( !Validate(filterContext) )
18: return;
19:
20: //### make sure the Type property is an Exception
21: if( Type != null && !typeof(System.Exception).IsAssignableFrom( Type ) )
22: throw new ArgumentException( "RedirectOnErrorAttribute's Type property's value must derive from System.Exception." );
23:
24: //### if no exception occurred, stop processing this filter
25: if( filterContext.Exception == null )
26: return;
27:
28: //### get inner exception unless it is null (this should never happen?)
29: Exception ex = filterContext.Exception.InnerException ?? filterContext.Exception;
30:
31: //### if exception was thrown because of Response.Redirect, ignore it
32: if( ex.GetType() == typeof(System.Threading.ThreadAbortException) )
33: return;
34: else if( Type == typeof(System.Threading.ThreadAbortException) )
35: throw new ArgumentException( "Cannot catch exceptions of type 'ThreadAbortException'." );
36:
37: //### if the specified Type matches the thrown exception, process it
38: if( IsExactMatch(ex) )
39: Redirect( filterContext );
40: //### if this attribute has no specified Type, investigate further (this attribute is a catch-all error handler)
41: else if( Type == null )
42: {
43:
44: //### loop through all other RedirectToUrlOnErrorAttribute on this method
45: foreach( RedirectOnErrorAttribute att in GetAllAttributes( filterContext ) )
46: //### ignore self
47: if( att.GetHashCode() == this.GetHashCode() )
48: continue;
49: //### if another catch-all attribute is found, throw an exception
50: else if( att.Type == null )
51: throw new ArgumentException( "Only one RedirectOnErrorAttribute per Action may be specified without its Type property provided." );
52: //### if an exact match is found, stop processing the catch-all. that attribute has priority
53: else if( att.IsExactMatch(ex) )
54: return;
55:
56: //### no exact matches were found. if the specified Type for the catch-all fits, process here
57: Redirect(filterContext);
58:
59: }
60: else
61: //### specified Type was not null, but did not match the thrown exception. don't process
62: return;
63:
64: }
65:
66: public bool IsExactMatch( Exception exception )
67: {
68: if( Type != null && exception.GetType() == Type )
69: return true;
70: else
71: return false;
72: }
73:
74: private List<RedirectOnErrorAttribute> GetAllAttributes( FilterExecutedContext filterContext )
75: {
76: return filterContext.ActionMethod
77: .GetCustomAttributes( typeof( RedirectOnErrorAttribute ), false )
78: .Select( a => a as RedirectOnErrorAttribute )
79: .ToList();
80: }
81:
82: protected abstract bool Validate( FilterExecutedContext filterContext );
83: protected abstract void Redirect( FilterExecutedContext filterContext );
84:
85: }
86: }