ASP.NETMVC集成EntLib实现“自动化”异常处理[实现篇]
通过《实例篇》的实演示可以看出我们通过扩展实现的自动异常处理机制能够利用EntLib的EHAB根据执行的一场处理策略对某个Action方法执行过程中抛出的异常进行处理。对于处理后的结果,则按照如下的机制对请求进行响应。[源代码从这里下载][本文已经同步到《HowASP.NETMVCWorks?》中]
对于Ajax请求,直接创建一个用于封装被处理后异常的数据对象,并据此创建一个JsonResult将异常信息回复给客户端。
对于非Ajax请求,如果当前Action方法上应用HandleErrorActionAttribute特性设置了匹配的Action方法用于处理该方法抛出的异常,那么执行该方法并用返回的ActionResult对象响应当前请求。
如果HandleErrorActionAttribute特性不曾应用在当前Action方法上,或者通过该特性指定的Action不存在,则将默认的错误View呈现出来作为多请求的响应。
目录
一、ExceptionPolicyAttribute&HandleErrorActionAttribute
二、实现在OnException方法中的异常处理逻辑
三、将处理后的错误消息存放在HttpContext的Items中
四、用于设置错误消息的ErrorMessageHandler
一、ExceptionPolicyAttribute&HandleErrorActionAttribute
所有的这些都是通过一个自定义的ExceptionFilter来实现的。不过我们并没有定义任何的ExceptionFilter特性,而是将异常处理实现在一个自定义的ExtendedController基类中,对异常的自动处理实现在重写的OnException方法中,不过在介绍该方法的逻辑之前我们先来看看定义在ExtendedController中的其他辅助成员。
1:publicclassExtendedController:Controller
2:{
3:privatestaticDictionarycontrollerDescriptors=newDictionary();
4:privatestaticobjectsyncHelper=newobject();
5:
6:protectedoverridevoidOnException(ExceptionContextfilterContext)
7:{
8://省略成员
9:}
10:
11://描述当前Controller的ControllerDescriptor
12:publicControllerDescriptorDescriptor
13:{
14:get
15:{
16:ControllerDescriptordescriptor;
17:if(controllerDescriptors.TryGetValue(this.GetType(),outdescriptor))
18:{
19:returndescriptor;
20:}
21:lock(syncHelper)
22:{
23:if(controllerDescriptors.TryGetValue(this.GetType(),outdescriptor))
24:{
25:returndescriptor;
26:}
27:else
28:{
29:descriptor=newReflectedControllerDescriptor(this.GetType());
30:controllerDescriptors.Add(this.GetType(),descriptor);
31:returndescriptor;
32:}
33:}
34:}
35:}
36://获取异常处理策略名称
37:publicstringGetExceptionPolicyName()
38:{
39:stringactionName=ControllerContext.RouteData.GetRequiredString("action");
40:ActionDescriptoractionDescriptor=this.Descriptor.FindAction(ControllerContext,actionName);
41:if(null==actionDescriptor)
42:{
43:returnstring.Empty;
44:}
45:ExceptionPolicyAttributeexceptionPolicyAttribute=actionDescriptor.GetCustomAttributes(true).OfType().FirstOrDefault()??
46:Descriptor.GetCustomAttributes(true).OfType().FirstOrDefault()??newExceptionPolicyAttribute("");
47:returnexceptionPolicyAttribute.ExceptionPolicyName;
48:}
49:
50://获取Handle-Error-Action名称
51:publicstringGetHandleErrorActionName()
52:{
53:stringactionName=ControllerContext.RouteData.GetRequiredString("action");
54:ActionDescriptoractionDescriptor=this.Descriptor.FindAction(ControllerContext,actionName);
55:if(null==actionDescriptor)
56:{
57:returnstring.Empty;
58:}
59:HandleErrorActionAttributehandleErrorActionAttribute=actionDescriptor.GetCustomAttributes(true).OfType().FirstOrDefault()??
60:Descriptor.GetCustomAttributes(true).OfType().FirstOrDefault()??newHandleErrorActionAttribute("");
61:returnhandleErrorActionAttribute.HandleErrorAction;
62:}
63:
64://用于执行Handle-Error-Action的ActionInvoker
65:publicHandleErrorActionInvokerHandleErrorActionInvoker{get;privateset;}
66:
67:publicExtendedController()
68:{
69:this.HandleErrorActionInvoker=newHandleErrorActionInvoker();
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:publicclassExceptionPolicyAttribute:Attribute
3:{
4:publicstringExceptionPolicyName{get;privateset;}
5:publicExceptionPolicyAttribute(stringexceptionPolicyName)
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:publicclassHandleErrorActionAttribute:Attribute
3:{
4:publicstringHandleErrorAction{get;privateset;}
5:publicHandleErrorActionAttribute(stringhandleErrorAction="")
6:{
7:this.HandleErrorAction=handleErrorAction;
8:}
9:}
通过HandleErrorActionAttribute特性设置的Handle-Error-Action需要手工执行以实现对当前请求的响应,为此我们创建了一个具有如下定义的HandleErrorActionInvoker。它是ControllerActionInvoker的子类,Handle-Error-Action需要手工执行以实现对当前请求的响应的执行通过虚方法InvokeActionMethod来完成。ExtendedController的HandleErrorActionInvoker返回的就是这样一个对象。
1:publicclassHandleErrorActionInvoker:ControllerActionInvoker
2:{
3:publicvirtualActionResultInvokeActionMethod(ControllerContextcontrollerContext,ActionDescriptoractionDescriptor)
4:{
5:IDictionaryparameterValues=this.GetParameterValues(controllerContext,actionDescriptor);
6:returnbase.InvokeActionMethod(controllerContext,actionDescriptor,parameterValues);
7:}
8:}
二、实现在OnException方法中的异常处理逻辑
整个异常处理和最终对请求的相应实现在如下所示的OnException方法中,流程并不复杂,在这里就不一一赘述了。不过对于整个处理流程,有两个点值得一提:其一,在调用EntLib的EHAB对异常处理过程中,允许相应的ExceptionHandler设置一个友好的错误消息,而这个消息被保存在当前HttpContext的Items中。其二,在调用异常处理方法之前,我们错误消息添加到当前的ModelState中,这也是为什么在上面的实例演示中错误消息会自动出现在ValidationSummary中的根本原因。
1:publicclassExtendedController:Controller
2:{
3://其他成员
4:protectedoverridevoidOnException(ExceptionContextfilterContext)
5:{
6://或者当前的ExceptionPolicy,如果不存在,则直接调用基类OnException方法
7:stringexceptionPolicyName=this.GetExceptionPolicyName();
8:if(string.IsNullOrEmpty(exceptionPolicyName))
9:{
10:base.OnException(filterContext);
11:return;
12:}
13:
14://利用EntLib的EHAB进行异常处理,并获取错误消息和最后抛出的异常
15:filterContext.ExceptionHandled=true;
16:ExceptionexceptionToThrow;
17:stringerrorMessage;
18:try
19:{
20:ExceptionPolicy.HandleException(filterContext.Exception,exceptionPolicyName,outexceptionToThrow);
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(newExceptionDetail(exceptionToThrow,errorMessage));
33:return;
34:}
35:
36://如果设置了匹配的HandleErrorAction,则调用之;
37://否则将ErrorView呈现出来
38:stringhandleErrorAction=this.GetHandleErrorActionName();
39:stringcontrollerName=ControllerContext.RouteData.GetRequiredString("controller");
40:stringwww.baiyuewang.netactionName=ControllerContext.RouteData.GetRequiredString("action");
41:errorMessage=string.IsNullOrEmpty(errorMessage)?exceptionToThrow.Message:errorMessage;
42:if(string.IsNullOrEmpty(handleErrorAction))
43:{
44:filterContext.Result=View("Error",newExtendedHandleErrorInfo(exceptionToThrow,controllerName,actionName,errorMessage));
45:}
46:else
47:{
48:ActionDescriptoractionDescriptor=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:publicstaticclassHttpContextExtensions
2:{
3:publicstaticstringkeyOfErrorMessage=Guid.NewGuid().ToString();
4:
5:publicstaticvoidSetErrorMessage(thisHttpContextcontext,stringerrorMessage)
6:{
7:context.Items[keyOfErrorMessage]=errorMessage;
8:}
9:
10:publicstaticstringGetErrorMessage(thisHttpContextcontext)
11:{
12:returncontext.Items[keyOfErrorMessage]asstring;
13:}
14:
15:publicstaticvoidClearErrorMessage(thisHttpContextcontext)
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:publicclassErrorMessageHandler:IExceptionHandler
3:{
4:publicstringErrorMessage{get;privateset;}
5:publicErrorMessageHandler(stringerrorMessage)
6:{
7:this.ErrorMessage=errorMessage;
8:}
9:publicExceptionHandleException(Exceptionexception,GuidhandlingInstanceId)
10:{
11:if(null!=HttpContext.Current)
12:{
13:HttpContext.Current.SetErrorMessage(this.ErrorMessage);
14:}
15:returnexception;
16:}
17:}
18:
19:publicclassErrorMessageHandlerData:ExceptionHandlerData
20:{
21:[ConfigurationProperty("errorMessage",IsRequired=true)]
22:publicstringErrorMessage
23:{
24:get{return(string)this["errorMessage"];}
25:set{this["errorMessage"]=value;}
26:}
27:
28:publicoverrideIEnumerableGetRegistrations(stringnamePrefix)
29:{
30:yieldreturnnewTypeRegistration(()=>newErrorMessageHandler(this.ErrorMessage))
31:{
32:Name=this.BuildName(namePrefix),
33:Lifetime=TypeRegistrationLifetime.Transient
34:};
35:}
36:}
|
|