[原创]Enterprise Library深入解析与灵活应用(4):创建一个自定义Exception Handler改变ELAB的异常处理机制...
一、背景与动机
微软Enterprise Library ELAB(Exception Handling Application Block)提供了一种基于策略(Policy)的异常处理方式,在不同的环境中,比如多层架构中不同的层次中,我们可以定义不同的异常处理策略。对于ELAB来说,Exception Handling Policy = Exception Type + Exception Handler(s) ,也就是说异常处理策略规定了对于某种类型的类型需要通过那些Exception Handler去处理。
从这种意义上讲,ELAB的异常处理机制是基于Exception Type的,异常的类型是我们处理异常的最小粒度。对于一个确定的异常处理策略,在不同场合抛出的同种类型的异常,都会使用相同的Exception Handler去处理。举个例子,对于一个基于SQL Server的Data Access操作,对于数据库连接失败和违反数据一致性约束,都会抛出SqlException。这两种情况对于ELAB都是等效的,因为它只能根据异常的类型进行异常处理。
在很多情况下,这种基于异常类型级别的异常处理并不能解决我们实际异常处理的问题。我们往往需要粒度更细的异常处理机制——对于抛出的同一种类型的异常,根据异常对象具体的属性值进行相应的异常处理。
二、从基于类型的异常处理到基于属性值异常处理
我们需要实现的目标很清楚,就是对于抛出的异常,我们可以根据某个属性的具体的值,为其指定对应的Exception Handler进行处理。由于ELAB基于异常类型的Exception Handler的分发机制,我们不能改变,我们只能采用一些变通的机制实现“曲线救国”,达到我们基于属性的分发Exception Handler的目的。
具体的实现方案就是创建一个特殊的Exception Handler,该Exception Handler根据异常对象某个属性的值,指定相应的Exception Handler。对于这个特殊的Exception Handler来说,他实现了基于属性值的筛选功能,我们把它命名为FilterableExceptionHandler。
一般情况下,异常的类型和对应的Exception Handler通过下图的形式直接进行匹配。当FooException抛出,两个Exception Handler,ExceptionHandlerA和ExceptionHandlerB先后被执行。
当引入了FilterableExceptionHandler以后,整个结构变成下面一种形式:FilterableExceptionHandler被指定到FooException,当FooException被抛出的时候,FilterableExceptionHandler被执行。而FilterableExceptionHandler本身并不执行异常处理相关的逻辑,它的工作是根据exception的某个属性值,创建相对应的ExceptionHandler(s),并使用他们来处理该异常。如下图所示,当exception.Property=Value1是,创建ExceptionHandlerA和ExceptionHandlerB处理异常;当exception.Property=Value2时,真正创建出来进行异常处理的是ExceptionHandlerC和ExceptionHandlerD。
三、FilterableExceptionHandler的配置
接下来,我们就来创建这样一个特殊的FilterableExceptionHandler。和一般的自定义Exception Handler一样,除了定义FilterableExceptionHandler本身之外,还需要定义两个辅助的类:ExceptionHandlerData和ExceptionHandlerAssembler,前者定义ExceptionHandler相关的配置信息;后者通过配置创建相应的ExceptionHandler。
我们先来定于FilterableExceptionHandler的ExceptionHandlerData:FilterableExceptionHandlerData。在这之前,我们来看看一个FilterableExceptionHandler配置的实例:
1: <?xml version="1.0" encoding="utf-8"?><!--CRLF-->
2: <configuration><!--CRLF-->
3: <configSections><!--CRLF-->
4: <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /><!--CRLF-->
5: </configSections><!--CRLF-->
6: <exceptionHandling><!--CRLF-->
7: <exceptionPolicies><!--CRLF-->
8: <add name="Exception Policy"><!--CRLF-->
9: <exceptionTypes><!--CRLF-->
10: <add type="Artech.CustomExceptionHandlers.FooException,Artech.CustomExceptionHandlers.Demo"<!--CRLF-->
11: postHandlingAction="ThrowNewException" name="Exception"><!--CRLF-->
12: <exceptionHandlers><!--CRLF-->
13: <add type="Artech.ExceptionHandlers.FilterableExceptionHandler,Artech.ExceptionHandlers" name="Custom Handler"><!--CRLF-->
14: <filters><!--CRLF-->
15: <add property="Message" value="xxx" name="filter1" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"><!--CRLF-->
16: <exceptionHandlers><!--CRLF-->
17: <add exceptionMessage="Bar" exceptionMessageResourceType=""<!--CRLF-->
18: replaceExceptionType="Artech.CustomExceptionHandlers.BarException,Artech.CustomExceptionHandlers.Demo"<!--CRLF-->
19: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"<!--CRLF-->
20: name="Replace Handler" /><!--CRLF-->
21: </exceptionHandlers><!--CRLF-->
22: </add><!--CRLF-->
23: <add property="Message" value="yyy" name="filter2" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"><!--CRLF-->
24: <exceptionHandlers><!--CRLF-->
25: <add exceptionMessage="Baz" exceptionMessageResourceType=""<!--CRLF-->
26: replaceExceptionType="Artech.CustomExceptionHandlers.BazException,Artech.CustomExceptionHandlers.Demo"<!--CRLF-->
27: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"<!--CRLF-->
28: name="Replace Handler" /><!--CRLF-->
29: </exceptionHandlers><!--CRLF-->
30: </add><!--CRLF-->
31: </filters><!--CRLF-->
32: </add><!--CRLF-->
33: </exceptionHandlers><!--CRLF-->
34: </add><!--CRLF-->
35: </exceptionTypes><!--CRLF-->
36: </add><!--CRLF-->
37: </exceptionPolicies><!--CRLF-->
38: </exceptionHandling><!--CRLF-->
39: </configuration><!--CRLF-->
40:
<!--CRLF-->
其中和FilterableExceptionHandler相关的配置集中在如下一段。整个配置的结果是这样的:<filters>中一个filter列表,定义了对异常对象属性名/属性值的筛选和符合该条件的Exception Handler列表。下面一段配置表达的场景是:对于抛出的异常(Artech.CustomExceptionHandlers.FooException,Artech.CustomExceptionHandlers.Demo),我们需要调用ReplaceHandler用一个另一个异常对其进行替换。具体的替换规则是:如何Message属性为“xxx”,则将其替换成BarException;如何Message属性为“yyy”,则替换成BazException。最终的Message分别为“Bar”和“Baz”。
1: <filters><!--CRLF-->
2: <add property="Message" value="xxx" name="filter1" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"><!--CRLF-->
3: <exceptionHandlers><!--CRLF-->
4: <add exceptionMessage="Bar" exceptionMessageResourceType=""<!--CRLF-->
5: replaceExceptionType="Artech.CustomExceptionHandlers.BarException,Artech.CustomExceptionHandlers.Demo"<!--CRLF-->
6: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"<!--CRLF-->
7: name="Replace Handler" /><!--CRLF-->
8: </exceptionHandlers><!--CRLF-->
9: </add><!--CRLF-->
10: <add property="Message" value="yyy" name="filter2" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"><!--CRLF-->
11: <exceptionHandlers><!--CRLF-->
12: <add exceptionMessage="Baz" exceptionMessageResourceType=""<!--CRLF-->
13: replaceExceptionType="Artech.CustomExceptionHandlers.BazException,Artech.CustomExceptionHandlers.Demo"<!--CRLF-->
14: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"<!--CRLF-->
15: name="Replace Handler" /><!--CRLF-->
16: </exceptionHandlers><!--CRLF-->
17: </add><!--CRLF-->
18: </filters><!--CRLF-->
四、如何创建如何创建FilterableExceptionHandler
对配置有一个初步了解后,我们来定义FilterableExceptionHandlerData:
1: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;<!--CRLF-->
2: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;<!--CRLF-->
3: using System.Configuration;<!--CRLF-->
4: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;<!--CRLF-->
5: namespace Artech.ExceptionHandlers<!--CRLF-->
6: {
<!--CRLF-->7: [Assembler(typeof(FilterableExceptionHandlerAssembler))]<!--CRLF-->
8: public class FilterableExceptionHandlerData:ExceptionHandlerData<!--CRLF-->
9: {
<!--CRLF-->10: [ConfigurationProperty("filters", IsRequired = true)]<!--CRLF-->
11: public NamedElementCollection<FilterEntry> Filters<!--CRLF-->
12: {
<!--CRLF--> 13: get
<!--CRLF--> 14: {
<!--CRLF-->15: return base["filters"] as NamedElementCollection<FilterEntry>;<!--CRLF-->