ASP.NET MVC集成EntLib异常处理实现
通过《ASP.NET MVC集成EntLib异常处理实例》的实演示可以看出我们通过扩展实现的自动异常处理机制能够利用EntLib的EHAB根据执行的一场处理策略对某个Action方法执行过程中抛出的异常进行处理。对于处理后的结果,则按照如下的机制对请求进行响应。[源代码从这里下载]
- 对于Ajax请求,直接创建一个用于封装被处理后异常的数据对象,并据此创建一个JsonResult将异常信息回复给客户端。
- 对于非Ajax请求,如果当前Action方法上应用HandleErrorActionAttribute特性设置了匹配的Action方法用于处理该方法抛出的异常,那么执行该方法并用返回的ActionResult对象响应当前请求。
- 如果HandleErrorActionAttribute特性不曾应用在当前Action方法上,或者通过该特性指定的Action不存在,则将默认的错误View呈现出来作为多请求的响应。
一、ExceptionPolicyAttribute & HandleErrorActionAttribute
所有的这些都是通过一个自定义的ExceptionFilter来实现的。不过我们并没有定义任何的ExceptionFilter特性,而是将异常处理实 现在一个自定义的ExtendedController基类中,对异常的自动处理实现在重写的OnException方法中,不过在介绍该方法的逻辑之前 我们先来看看定义在ExtendedController中的其他辅助成员。
1: public class ExtendedController: Controller 2: { 3: private static Dictionary<Type, ControllerDescriptor> controllerDescriptors = new Dictionary<Type, ControllerDescriptor>(); 4: private static object syncHelper = new object(); 5: 6: protected override void OnException(ExceptionContext filterContext) 7: { 8: //省略成员 9: } 10: 11: //描述当前Controller的ControllerDescriptor 12: public ControllerDescriptor Descriptor 13: { 14: get 15: { 16: ControllerDescriptor descriptor; 17: if(controllerDescriptors.TryGetValue(this.GetType(), out descriptor)) 18: { 19: return descriptor; 20: } 21: lock (syncHelper) 22: { 23: if (controllerDescriptors.TryGetValue(this.GetType(), out descriptor)) 24: { 25: return descriptor; 26: } 27: else 28: { 29: descriptor = new ReflectedControllerDescriptor(this.GetType()); 30: controllerDescriptors.Add(this.GetType(), descriptor); 31: return descriptor; 32: } 33: } 34: } 35: } 36: //获取异常处理策略名称 37: public string GetExceptionPolicyName() 38: { 39: string actionName = ControllerContext.RouteData.GetRequiredString("action"); 40: ActionDescriptor actionDescriptor = this.Descriptor.FindAction(ControllerContext, actionName); 41: if (null == actionDescriptor) 42: { 43: return string.Empty; 44: } 45: ExceptionPolicyAttribute exceptionPolicyAttribute = actionDescriptor.GetCustomAttributes(true).OfType<ExceptionPolicyAttribute>().FirstOrDefault()?? 46: Descriptor.GetCustomAttributes(true).OfType<ExceptionPolicyAttribute>().FirstOrDefault()?? new ExceptionPolicyAttribute(""); 47: return exceptionPolicyAttribute.ExceptionPolicyName; 48: } 49: 50: //获取Handle-Error-Action名称 51: public string GetHandleErrorActionName() 52: { 53: string actionName = ControllerContext.RouteData.GetRequiredString("action"); 54: ActionDescriptor actionDescriptor = this.Descriptor.FindAction(ControllerContext, actionName); 55: if (null == actionDescriptor) 56: { 57: return string.Empty; 58: } 59: HandleErrorActionAttribute handleErrorActionAttribute = actionDescriptor.GetCustomAttributes(true).OfType<HandleErrorActionAttribute>().FirstOrDefault()?? 60: Descriptor.GetCustomAttributes(true).OfType<HandleErrorActionAttribute>().FirstOrDefault()?? new HandleErrorActionAttribute(""); 61: return handleErrorActionAttribute.HandleErrorAction; 62: } 63: 64: //用于执行Handle-Error-Action的ActionInvoker 65: public HandleErrorActionInvoker HandleErrorActionInvoker { get; private set; } 66: 67: public ExtendedController() 68: { 69: this.HandleErrorActionInvoker = new HandleErrorActionInvoker(); 70: } 71: }
ExtendedController的Descriptor属性用于返回描述自身的ControllerDescriptor对象,实际上是一个 ReflectedControllerDescriptor对象。为了避免频繁的反射操作造成对性能的影响,我们将基于某个类型解析出来的 ReflectedControllerDescriptor对象进行了全局性缓存。
GetExceptionPolicyName方法用于返回当前采用的异常处理策略名称。异常处理策略名称是通过具有如下定义的 ExceptionPolicyAttribute特性来指定的。该特性既可以应用在Controller类型上,也可以应用在Action方法上,换句 话说,我们可以采用不同的策略来处理从不同Action执行过程中抛出的异常。GetExceptionPolicyName利用 ControllerDesctior和ActionDescriptor可以很容易地得到应用的ExceptionPolicyAttribute特 性,进而得到相应的异常处理策略名称。
1: [AttributeUsage( AttributeTargets.Class| AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 2: public class ExceptionPolicyAttribute: Attribute 3: { 4: public string ExceptionPolicyName { get; private set; } 5: public ExceptionPolicyAttribute(string exceptionPolicyName) 6: { 7: this.ExceptionPolicyName = exceptionPolicyName; 8: } 9: }
另一个方法GetHandleErrorActionName用于获取通过应用在Action方法上的特性 HandleErrorActionAttribute设置的Handle-Error-Action的名称。该特性定义如下,它既可以应用于某个 Action方法,也可以应用于Controller类。GetHandleErrorActionName方法同样利用 ControllerDesctior和ActionDescriptor得到应用的ExceptionPolicyAttribute特性,并最终得到 对应的异常处理Action名称。
1: [AttributeUsage( AttributeTargets.Class| AttributeTargets.Method, AllowMultiple = false)] 2: public class HandleErrorActionAttribute: Attribute 3: { 4: public string HandleErrorAction { get; private set; } 5: public HandleErrorActionAttribute(string handleErrorAction = "") 6: { 7: this.HandleErrorAction = handleErrorAction; 8: } 9: }
通过HandleErrorActionAttribute特性设置的Handle-Error-Action需要手工执行以实现对当前请求的响 应,为此我们创建了一个具有如下定义的HandleErrorActionInvoker。它是ControllerActionInvoker的子 类,Handle-Error-Action需要手工执行以实现对当前请求的响应的执行通过虚方法InvokeActionMethod来完成。 ExtendedController的HandleErrorActionInvoker返回的就是这样一个对象。
1: public class HandleErrorActionInvoker: ControllerActionInvoker 2: { 3: public virtual ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor) 4: { 5: IDictionary<string, object> parameterValues = this.GetParameterValues(controllerContext, actionDescriptor); 6: return base.InvokeActionMethod(controllerContext, actionDescriptor, parameterValues); 7: } 8: }
二、实现在OnException方法中的异常处理逻辑
整个异常处理和最终对请求的相应实现在如下所示的OnException方法中,流程并不复杂,在这里就不一一赘述了。不过对于整个处理流程,有两 个点值得一提:其一,在调用EntLib的EHAB对异常处理过程中,允许相应的ExceptionHandler设置一个友好的错误消息,而这个消息被 保存在当前HttpContext的Items中。其二,在调用异常处理方法之前,我们错误消息添加到当前的ModelState中,这也是为什么在上面 的实例演示中错误消息会自动出现在ValidationSummary中的根本原因。
1: public class ExtendedController: Controller 2: { 3: //其他成员 4: protected override void OnException(ExceptionContext filterContext) 5: { 6: //或者当前的ExceptionPolicy,如果不存在,则直接调用基类OnException方法 7: string exceptionPolicyName = this.GetExceptionPolicyName(); 8: if (string.IsNullOrEmpty(exceptionPolicyName)) 9: { 10: base.OnException(filterContext); 11: return; 12: } 13: 14: //利用EntLib的EHAB进行异常处理,并获取错误消息和最后抛出的异常 15: filterContext.ExceptionHandled = true; 16: Exception exceptionToThrow; 17: string errorMessage; 18: try 19: { 20: ExceptionPolicy.HandleException(filterContext.Exception,exceptionPolicyName, out exceptionToThrow); 21: errorMessage = System.Web.HttpContext.Current.GetErrorMessage(); 22: } 23: finally 24: { 25: System.Web.HttpContext.Current.ClearErrorMessage(); 26: } 27: exceptionToThrow = exceptionToThrow ?? filterContext.Exception; 28: 29: //对于Ajax请求,直接返回一个用于封装异常的JsonResult 30: if (Request.IsAjaxRequest()) 31: { 32: filterContext.Result = Json(new ExceptionDetail(exceptionToThrow, errorMessage)); 33: return; 34: } 35: 36: //如果设置了匹配的HandleErrorAction,则调用之; 37: //否则将Error View呈现出来 38: string handleErrorAction = this.GetHandleErrorActionName(); 39: string controllerName = ControllerContext.RouteData.GetRequiredString("controller"); 40: string actionName = ControllerContext.RouteData.GetRequiredString("action"); 41: errorMessage = string.IsNullOrEmpty(errorMessage) ? exceptionToThrow.Message : errorMessage; 42: if (string.IsNullOrEmpty(handleErrorAction)) 43: { 44: filterContext.Result = View("Error", new ExtendedHandleErrorInfo(exceptionToThrow, controllerName, actionName, errorMessage)); 45: } 46: else 47: { 48: ActionDescriptor actionDescriptor = Descriptor.FindAction(ControllerContext, handleErrorAction); 49: ModelState.AddModelError("", errorMessage); 50: filterContext.Result = this.HandleErrorActionInvoker.InvokeActionMethod(ControllerContext, actionDescriptor); 51: } 52: } 53: }
三、将处理后的错误消息存放在HttpContext的Items中
在调用EntLib的EHAB进行异常处理之后从当前HttpContext提取错误消息,以及最后清除消息分别是通过HttpContext的扩 展方法GetErrorMessage和ClearErrorMessage实现的。如下面的代码片断所示,除了这两个扩展方法我们还定义了另一个用于设 置错误消息的SetErrorMessage方法。
1: public static class HttpContextExtensions 2: { 3: public static string keyOfErrorMessage = Guid.NewGuid().ToString(); 4: 5: public static void SetErrorMessage(this HttpContext context, string errorMessage) 6: { 7: context.Items[keyOfErrorMessage]=errorMessage; 8: } 9: 10: public static string GetErrorMessage(this HttpContext context) 11: { 12: return context.Items[keyOfErrorMessage] as string; 13: } 14: 15: public static void ClearErrorMessage(this HttpContext context) 16: { 17: if (context.Items.Contains(keyOfErrorMessage)) 18: { 19: context.Items.Remove(keyOfErrorMessage); 20: } 21: } 22: }
四、用于设置错误消息的ErrorMessageHandler
用于设置错误信息的ErrorMessageHandler以及对应配置元素类型ErrorMessageHandlerData定义如下。 ErrorMessageHandler表示错误消息的ErrorMessage属性在构造函数中被初始化,而在实现的HandleException方 法中直接通过调用当前HttpContext的扩展方法SetErrorMessage进行错误消息的设置。
1: [ConfigurationElementType(typeof(ErrorMessageHandlerData))] 2: public class ErrorMessageHandler: IExceptionHandler 3: { 4: public string ErrorMessage { get; private set; } 5: public ErrorMessageHandler(string errorMessage) 6: { 7: this.ErrorMessage = errorMessage; 8: } 9: public Exception HandleException(Exception exception, Guid handlingInstanceId) 10: { 11: if (null != HttpContext.Current) 12: { 13: HttpContext.Current.SetErrorMessage(this.ErrorMessage); 14: } 15: return exception; 16: } 17: } 18: 19: public class ErrorMessageHandlerData : ExceptionHandlerData 20: { 21: [ConfigurationProperty("errorMessage", IsRequired=true)] 22: public string ErrorMessage 23: { 24: get { return (string)this["errorMessage"]; } 25: set { this["errorMessage"] = value; } 26: } 27: 28: public override IEnumerable<TypeRegistration> GetRegistrations(string namePrefix) 29: { 30: yield return new TypeRegistration<IExceptionHandler>(() => new ErrorMessageHandler(this.ErrorMessage)) 31: { 32: Name = this.BuildName(namePrefix), 33: Lifetime = TypeRegistrationLifetime.Transient 34: }; 35: } 36: }
作者:Artech
出处:http://artech.cnblogs.com/
加支付宝好友偷能量挖...