Enterprise Library深入解析與靈活應用(4):創建一個自定義Exception Handler改變ELAB的異常處理機制
1、背景與動機
微軟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都是等效的,因為它只能根據異常的類型進行異 常處理。
在很多情況下,這種基於異常類型級別的異常處理並不能解決我們實際異常處理的問題。我們往往需要粒度更細的異常處理機 制——對於拋出的同一種類型的異常,根據異常對象具體的屬性值進行相應的異常處理。
2、從基於類型的異常處理到基於 屬性值異常處理
我們需要實現的目標很清楚,就是對於拋出的異常,我們可以根據某個屬性的具體的值,為其指定對應的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。
3、FilterableExceptionHandler的配置
接下來,我們就來創建這樣一個特殊的FilterableExceptionHandler。和一般的自定義Exception Handler一樣,除了定義 FilterableExceptionHandler本身之外,還需要定義兩個輔助的類:ExceptionHandlerData和ExceptionHandlerAssembler,前者定義 ExceptionHandler相關的配置信息;後者通過配置創建相應的ExceptionHandler。
我們先來定於FilterableExceptionHandler的 ExceptionHandlerData:FilterableExceptionHandlerData。在這之前,我們來看看一個FilterableExceptionHandler配置的實例:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>
<exceptionHandling>
<exceptionPolicies>
<add name="Exception Policy">
<exceptionTypes>
<add type="Artech.CustomExceptionHandlers.FooException,Artech.CustomExceptionHandlers.Demo"
postHandlingAction="ThrowNewException" name="Exception">
<exceptionHandlers>
<addtype="Artech.ExceptionHandlers.FilterableExceptionHandler,Artech.ExceptionHandlers" name="Custom Handler">
<filters>
<add property="Message" value="xxx" name="filter1" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<exceptionHandlers>
<add exceptionMessage="Bar" exceptionMessageResourceType=""
replaceExceptionType="Artech.CustomExceptionHandlers.BarException,Artech.CustomExceptionHandlers.Demo"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Replace Handler" />
</exceptionHandlers>
</add>
<add property="Message" value="yyy" name="filter2" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<exceptionHandlers>
<add exceptionMessage="Baz" exceptionMessageResourceType=""
replaceExceptionType="Artech.CustomExceptionHandlers.BazException,Artech.CustomExceptionHandlers.Demo"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Replace Handler" />
</exceptionHandlers>
</add>
</filters>
</add>
</exceptionHandlers>
</add>
</exceptionTypes>
</add>
</exceptionPolicies>
</exceptionHandling>
</configuration>
其中和FilterableExceptionHandler相關的配置集中在如下一段。整個配置的結果是這樣的: <filters>中一個filter列表,定義了對異常對象屬性名/屬性值的篩選和符合該條件的Exception Handler列表。下面一段配置表達的場 景是:對於拋出的異常(Artech.CustomExceptionHandlers.FooException,Artech.CustomExceptionHandlers.Demo),我們需要調用 ReplaceHandler用一個另一個異常對其進行替換。具體的替換規則是:如何Message屬性為“xxx”,則將其替換成BarException; 如何Message屬性為“yyy”,則替換成BazException。最終的Message分別為“Bar”和“Baz”。
<filters>
<add property="Message" value="xxx" name="filter1" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<exceptionHandlers>
<add exceptionMessage="Bar" exceptionMessageResourceType=""
replaceExceptionType="Artech.CustomExceptionHandlers.BarException,Artech.CustomExceptionHandlers.Demo"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Replace Handler" />
</exceptionHandlers>
</add>
<add property="Message" value="yyy" name="filter2" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<exceptionHandlers>
<add exceptionMessage="Baz" exceptionMessageResourceType=""
replaceExceptionType="Artech.CustomExceptionHandlers.BazException,Artech.CustomExceptionHandlers.Demo"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Replace Handler" />
</exceptionHandlers>
</add>
</filters>
4、如何創建如何創建FilterableExceptionHandler
對配置有一個初步了 解後,我們來定義FilterableExceptionHandlerData:
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using System.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
namespace Artech.ExceptionHandlers
{
[Assembler(typeof(FilterableExceptionHandlerAssembler))]
public class FilterableExceptionHandlerData:ExceptionHandlerData
{
[ConfigurationProperty("filters", IsRequired = true)]
public NamedElementCollection<FilterEntry> Filters
{
get
{
return base["filters"] as NamedElementCollection<FilterEntry>;
}
set
{
this["filters"] = value;
}
}
}
}
FilterableExceptionHandlerData僅僅是FilterEntry的集合。我們接著來看看 FilterEntry的定義:
using System;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using System.Configuration;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
using System.ComponentModel;
namespace Artech.ExceptionHandlers
{
public class FilterEntry : NamedConfigurationElement
{
[ConfigurationProperty("property", IsRequired = true)]
public string PropertyName
{
get
{
return this ["property"] as string;
}
set
{
this ["property"] = value;
}
}
[ConfigurationProperty ("value", IsRequired=true)]
public string PropertyValue
{
get
{
return this["value"] as string;
}
set
{
this["value"] = value;
}
}
[ConfigurationProperty("typeConverter",IsRequired = false, DefaultValue = "")]
public string TypeConverterData
{
get
{
return this ["typeConverter"] as string;
}
set
{
this ["typeConverter"] = value;
}
}
public TypeConverter TypeConverter
{
get
{
if (string.IsNullOrEmpty (this.TypeConverterData))
{
return new TypeConverter();
}
Type typeConverterType = null;
try
{
typeConverterType = Type.GetType(this.TypeConverterData);
}
catch (Exception ex)
{
throw new ConfigurationErrorsException(ex.Message);
}
TypeConverter typeConverter = Activator.CreateInstance(typeConverterType) as TypeConverter;
if (typeConverter == null)
{
throw new ConfigurationErrorsException(string.Format("The {0} is not a valid TypeConverter.",this.TypeConverterData));
}
return typeConverter;
}
}
[ConfigurationProperty("exceptionHandlers")]
public NameTypeConfigurationElementCollection<ExceptionHandlerData, CustomHandlerData> ExceptionHandlers
{
get
{
return (NameTypeConfigurationElementCollection<ExceptionHandlerData, CustomHandlerData>)this ["exceptionHandlers"];
}
}
}
}
由於我們需要根據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 的定義。
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
namespace Artech.ExceptionHandlers
{
public class FilterableExceptionHandlerAssembler : IAssembler<IExceptionHandler, ExceptionHandlerData>
{
#region IAssembler<IExceptionHandler,ExceptionHandlerData> Members
public IExceptionHandler Assemble(IBuilderContext context, ExceptionHandlerData objectConfiguration,
IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
{
return new FilterableExceptionHandler(objectConfiguration as FilterableExceptionHandlerData);
}
#endregion
}
}
5、使用、驗證FilterableExceptionHandler
現在我們通過一個簡單的Console Application來驗證FilterableExceptionHandler是否能夠按照我們希望的方式進行工作。我們使用在第三節列出的配置。為次我們我需要創建 3個Exception:FooException 、BarException 和BazException。
using System;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using System.Runtime.Serialization;
namespace Artech.CustomExceptionHandlers
{
[global::System.Serializable]
public class FooException : Exception
{
public FooException() { }
public FooException(string message) : base(message) { }
public FooException(string message, Exception inner) : base(message, inner) { }
protected FooException(SerializationInfo info,StreamingContext context)
: base(info, context) { }
}
[global::System.Serializable]
public class BarException : Exception
{
public BarException() { }
public BarException(string message) : base(message) { }
public BarException(string message, Exception inner) : base(message, inner) { }
protected BarException(SerializationInfo info,StreamingContext context)
: base(info, context) { }
}
[global::System.Serializable]
public class BazException : Exception
{
public BazException() { }
public BazException(string message) : base(message) { }
public BazException(string message, Exception inner) : base(message, inner) { }
protected BazException(SerializationInfo info,StreamingContext context)
: base(info, context) { }
}
}
在通過配置我們可以看到,我們希望的是對FooException 進行異常的處理,並通過Message的屬性,通過 ReplaceHandler將其替換成BarException 和BazException;為此我們寫一個HandleException方法。如下所以,我們人為地拋出一個 FooException,Message通過參數指定。在try/catch中,通過ExceptionPolicy.HandleException方法通過 ELAB進行異常的處理。在最外層的 catch中,輸出最終的Exception的類型和Message。在Main方法中,兩次調用HandleException方法,在參數中指定FooException的Message (“xxx”和“yyy”)。
using System;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using System.Runtime.Serialization;
namespace Artech.CustomExceptionHandlers
{
class Program
{
static void Main(string[] args)
{
HandleException("xxx");
HandleException("yyy");
}
private static void HandleException(string message)
{
try
{
try
{
throw new FooException(message);
}
catch (Exception ex)
{
if (ExceptionPolicy.HandleException (ex, "Exception Policy"))
{
throw;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception Type: {0}; Message: {1}", ex.GetType().Name, ex.Message);
}
}
}
}
運行該程序,你將會看到如下的輸出結果。可見對應拋出的同一種類型的Exception(FooException ),通過我們的FilterableExceptionHandler,根據Message屬性值的不同,最終被分別替換成了BarException 和BazException。
本文配套源碼