一、背景与动机
微软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"?>
2: <configuration>
3: <configSections>
4: <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
5: </configSections>
6: <exceptionHandling>
7: <exceptionPolicies>
8: <add name="Exception Policy">
9: <exceptionTypes>
10: <add type="Artech.CustomExceptionHandlers.FooException,Artech.CustomExceptionHandlers.Demo"
11: postHandlingAction="ThrowNewException" name="Exception">
12: <exceptionHandlers>
13: <add type="Artech.ExceptionHandlers.FilterableExceptionHandler,Artech.ExceptionHandlers" name="Custom Handler">
14: <filters>
15: <add property="Message" value="xxx" name="filter1" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
16: <exceptionHandlers>
17: <add exceptionMessage="Bar" exceptionMessageResourceType=""
18: replaceExceptionType="Artech.CustomExceptionHandlers.BarException,Artech.CustomExceptionHandlers.Demo"
19: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
20: name="Replace Handler" />
21: </exceptionHandlers>
22: </add>
23: <add property="Message" value="yyy" name="filter2" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
24: <exceptionHandlers>
25: <add exceptionMessage="Baz" exceptionMessageResourceType=""
26: replaceExceptionType="Artech.CustomExceptionHandlers.BazException,Artech.CustomExceptionHandlers.Demo"
27: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
28: name="Replace Handler" />
29: </exceptionHandlers>
30: </add>
31: </filters>
32: </add>
33: </exceptionHandlers>
34: </add>
35: </exceptionTypes>
36: </add>
37: </exceptionPolicies>
38: </exceptionHandling>
39: </configuration>
40:
其中和FilterableExceptionHandler相关的配置集中在如下一段。整个配置的结果是这样的:<filters>中一个filter列表,定义了对异常对象属性名/属性值的筛选和符合该条件的Exception Handler列表。下面一段配置表达的场景是:对于抛出的异常(Artech.CustomExceptionHandlers.FooException,Artech.CustomExceptionHandlers.Demo),我们需要调用ReplaceHandler用一个另一个异常对其进行替换。具体的替换规则是:如何Message属性为“xxx”,则将其替换成BarException;如何Message属性为“yyy”,则替换成BazException。最终的Message分别为“Bar”和“Baz”。
1: <filters>
2: <add property="Message" value="xxx" name="filter1" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
3: <exceptionHandlers>
4: <add exceptionMessage="Bar" exceptionMessageResourceType=""
5: replaceExceptionType="Artech.CustomExceptionHandlers.BarException,Artech.CustomExceptionHandlers.Demo"
6: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
7: name="Replace Handler" />
8: </exceptionHandlers>
9: </add>
10: <add property="Message" value="yyy" name="filter2" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
11: <exceptionHandlers>
12: <add exceptionMessage="Baz" exceptionMessageResourceType=""
13: replaceExceptionType="Artech.CustomExceptionHandlers.BazException,Artech.CustomExceptionHandlers.Demo"
14: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
15: name="Replace Handler" />
16: </exceptionHandlers>
17: </add>
18: </filters>
四、如何创建如何创建FilterableExceptionHandler
对配置有一个初步了解后,我们来定义FilterableExceptionHandlerData:
1: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
2: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
3: using System.Configuration;
4: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
5: namespace Artech.ExceptionHandlers
6: {
7: [Assembler(typeof(FilterableExceptionHandlerAssembler))]
8: public class FilterableExceptionHandlerData:ExceptionHandlerData
9: {
10: [ConfigurationProperty("filters", IsRequired = true)]
11: public NamedElementCollection<FilterEntry> Filters
12: {
13: get
14: {
15: return base["filters"] as NamedElementCollection<FilterEntry>;
16: }
17: set
18: {
19: this["filters"] = value;
20: }
21: }
22: }
23: }
FilterableExceptionHandlerData仅仅是FilterEntry的集合。我们接着来看看FilterEntry的定义:
1: using System;
2: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
3: using System.Configuration;
4: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
5: using System.ComponentModel;
6: namespace Artech.ExceptionHandlers
7: {
8: public class FilterEntry : NamedConfigurationElement
9: {
10: [ConfigurationProperty("property", IsRequired = true)]
11: public string PropertyName
12: {
13: get
14: {
15: return this["property"] as string;
16: }
17: set
18: {
19: this["property"] = value;
20: }
21: }
22:
23: [ConfigurationProperty("value", IsRequired = true)]
24: public string PropertyValue
25: {
26: get
27: {
28: return this["value"] as string;
29: }
30: set
31: {
32: this["value"] = value;
33: }
34: }
35: [ConfigurationProperty("typeConverter", IsRequired = false, DefaultValue = "")]
36: public string TypeConverterData
37: {
38: get
39: {
40: return this["typeConverter"] as string;
41: }
42: set
43: {
44: this["typeConverter"] = value;
45: }
46: }
47: public TypeConverter TypeConverter
48: {
49: get
50: {
51: if (string.IsNullOrEmpty(this.TypeConverterData))
52: {
53: return new TypeConverter();
54: }
55:
56: Type typeConverterType = null;
57: try
58: {
59: typeConverterType = Type.GetType(this.TypeConverterData);
60: }
61: catch (Exception ex)
62: {
63: throw new ConfigurationErrorsException(ex.Message);
64: }
65: TypeConverter typeConverter = Activator.CreateInstance(typeConverterType) as TypeConverter;
66: if (typeConverter == null)
67: {
68: throw new ConfigurationErrorsException(string.Format("The {0} is not a valid TypeConverter.", this.TypeConverterData));
69: }
70:
71: return typeConverter;
72: }
73: }
74:
75: [ConfigurationProperty("exceptionHandlers")]
76: public NameTypeConfigurationElementCollection<ExceptionHandlerData, CustomHandlerData> ExceptionHandlers
77: {
78: get
79: {
80: return (NameTypeConfigurationElementCollection<ExceptionHandlerData, CustomHandlerData>)this["exceptionHandlers"];
81: }
82: }
83: }
84: }
由于我们需要根据exception的某个属性来动态指定具体的ExceptionHandler,我们定了3个必要的属性:PropertyName、PropertyValue和ExceptionHandlers。他们分别表示用于筛选的属性名称和属性,以及满足筛选条件所采用的Exception Handler的配置。此外还具有一个额外的属性:TypeConverter,用于类型的转化。在进行筛选比较的时候,我们通过反射得到exception某个属性(PropertyName)的值,然后和指定的值(PropertyValue)进行比较。简单起见,我们在这里进行字符串的比较,所以我们需要通过TypeConverter将通过反射得到的属性值转换成字符串。默认的TypeConverter为System.ComponentModel.TypeConverter。
接下来我们看看真正的FilterableExceptionHandler的定义:
using System;
using System.Collections.Generic;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using System.Reflection;
using System.Configuration;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
namespace Artech.ExceptionHandlers
{
[ConfigurationElementType(typeof(FilterableExceptionHandlerData))]
public class FilterableExceptionHandler : IExceptionHandler
{
private FilterableExceptionHandlerData _filterableExceptionHandlerData;
public FilterableExceptionHandler(FilterableExceptionHandlerData handlerData)
{
this._filterableExceptionHandlerData = handlerData;
}
private IList<IExceptionHandler> GetFilteredHandler(Exception exception, FilterableExceptionHandlerData handlerData)
{
IList<IExceptionHandler> handlers = new List<IExceptionHandler>();
foreach (FilterEntry filterEntry in handlerData.Filters)
{
PropertyInfo propertyInfo = exception.GetType().GetProperty(filterEntry.PropertyName);
if (propertyInfo == null)
{
throw new ConfigurationErrorsException(
string.Format("The {0} does not have the {1} property.", exception.GetType().Name, filterEntry.PropertyName));
}
object propertyValue = propertyInfo.GetValue(exception, null);
if (string.Compare(filterEntry.TypeConverter.ConvertToString(propertyValue), filterEntry.PropertyValue, true) == 0)
{
foreach(ExceptionHandlerData exceptionHandlerData in filterEntry.ExceptionHandlers)
{
handlers.Add( ExceptionHandlerCustomFactory.Instance.Create(null,exceptionHandlerData,null,null));
}
}
}
return handlers;
}
#region IExceptionHandler Members
public Exception HandleException(Exception exception, Guid handlingInstanceId)
{
foreach (IExceptionHandler handler in this.GetFilteredHandler(exception, this._filterableExceptionHandlerData))
{
exception = handler.HandleException(exception,handlingInstanceId);
}
return exception;
}
#endregion
}
}
FilterableExceptionHandler的构造函数接受一个FilterableExceptionHandlerData 参数。在GetFilteredHandler方法中,我们通过具体的Exception对象和FilterableExceptionHandlerData筛选出真正的ExceptionHandler。逻辑并不复杂:便利FilterableExceptionHandlerData 中的所有FilterEntry,通过反射得到FilterEntry指定的属性名称(PropertyName)对应的属性值;通过TypeConverter转化成字符串后和FilterEntry指定的属性值(PropertyValue)进行比较。如果两者相互匹配,得到FilterEntry所有ExceptionHandler的ExceptionHandlerData,通过ExceptionHandlerCustomFactory创建对应的Exception Handler对象。最后将创建的Exception Handler对象加入目标列表。
在HandleException方法中,只需要逐个执行通过GetFilteredHandler方法筛选出来的Exception Handler就可以了。
最后简单看看FilterableExceptionHandlerAssembler 的定义。
1: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
2: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
3: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
4: using Microsoft.Practices.ObjectBuilder2;
5: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
6:
7: namespace Artech.ExceptionHandlers
8: {
9: public class FilterableExceptionHandlerAssembler : IAssembler<IExceptionHandler, ExceptionHandlerData>
10: {
11: #region IAssembler<IExceptionHandler,ExceptionHandlerData> Members
12:
13: public IExceptionHandler Assemble(IBuilderContext context, ExceptionHandlerData objectConfiguration,
14: IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
15: {
16: return new FilterableExceptionHandler(objectConfiguration as FilterableExceptionHandlerData);
17: }
18:
19: #endregion
20: }
21: }
22:
五、使用、验证FilterableExceptionHandler
现在我们通过一个简单的Console Application来验证FilterableExceptionHandler是否能够按照我们希望的方式进行工作。我们使用在第三节列出的配置。为次我们我需要创建3个Exception:FooException 、BarException 和BazException。
1: using System;
2: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
3: using System.Runtime.Serialization;
4: namespace Artech.CustomExceptionHandlers
5: {
6: [global::System.Serializable]
7: public class FooException : Exception
8: {
9: public FooException() { }
10: public FooException(string message) : base(message) { }
11: public FooException(string message, Exception inner) : base(message, inner) { }
12: protected FooException(SerializationInfo info,StreamingContext context)
13: : base(info, context) { }
14: }
15: [global::System.Serializable]
16: public class BarException : Exception
17: {
18: public BarException() { }
19: public BarException(string message) : base(message) { }
20: public BarException(string message, Exception inner) : base(message, inner) { }
21: protected BarException(SerializationInfo info,StreamingContext context)
22: : base(info, context) { }
23: }
24: [global::System.Serializable]
25: public class BazException : Exception
26: {
27: public BazException() { }
28: public BazException(string message) : base(message) { }
29: public BazException(string message, Exception inner) : base(message, inner) { }
30: protected BazException(SerializationInfo info,StreamingContext context)
31: : base(info, context) { }
32: }
33: }
在通过配置我们可以看到,我们希望的是对FooException 进行异常的处理,并通过Message的属性,通过ReplaceHandler将其替换成BarException 和BazException;为此我们写一个HandleException方法。如下所以,我们人为地抛出一个FooException,Message通过参数指定。在try/catch中,通过ExceptionPolicy.HandleException方法通过 ELAB进行异常的处理。在最外层的catch中,输出最终的Exception的类型和Message。在Main方法中,两次调用HandleException方法,在参数中指定FooException的Message(“xxx”和“yyy”)。
1: using System;
2: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
3: using System.Runtime.Serialization;
4: namespace Artech.CustomExceptionHandlers
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: HandleException("xxx");
11: HandleException("yyy");
12: }
13: private static void HandleException(string message)
14: {
15: try
16: {
17: try
18: {
19: throw new FooException(message);
20: }
21: catch (Exception ex)
22: {
23: if (ExceptionPolicy.HandleException(ex, "Exception Policy"))
24: {
25: throw;
26: }
27: }
28: }
29: catch (Exception ex)
30: {
31: Console.WriteLine("Exception Type: {0}; Message: {1}", ex.GetType().Name, ex.Message);
32: }
33: }
34: }
35: }
运行该程序,你将会看到如下的输出结果。可见对应抛出的同一种类型的Exception(FooException),通过我们的FilterableExceptionHandler,根据Message属性值的不同,最终被分别替换成了BarException 和BazException。