Exception Handling Application Block与WCF
Exception Handling Application Block与WCF
当前的项目同时用到WCF和Enterprise Library3.1里的Exception Handling Application Block(EHAB),遇到了如下问题:
1. 如果在WCF客户端调用WCF服务端代码时,WCF服务端发生了异常,如何用EHAB在WCF服务端处理这个异常使WCF客户端知道?
2. WCF服务端抛出的异常传递到WCF客户端后如何用EHAB处理?
Contract
WCF提供了includeExceptionDetailInFaults配置项,通过配置这个配置项为true,WCF服务端的任何异常都可以传递到WCF的客户端:
<behaviors >
<serviceBehaviors >
<behavior name ="debug">
<serviceDebug includeExceptionDetailInFaults ="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
但在生产环境,这样做是不安全的。这会使一些敏感信息跨过服务边界,从而对应用程序的安全不利。所以includeExceptionDetailInFaults配置项只能在开发阶段debug时使用,生产环境下includeExceptionDetailInFaults应该设为false。
WCF提供了FaultContract,通过它我们可以定义能够在soap中传递的错误类型(即使includeExceptionDetailInFaults配置项设为false)。我们知道下面代码可以定义WCF的ServiceContract和OperationContract:
[ServiceContract]
public interface IMath
{
[OperationContract]
double Divide(double x, double y);
}
如果我们想把Divide方法可能的错误从WCF服务端传到WCF客户端,我们就要增加一个FaultContract特性:
[ServiceContract]
public interface IMath
{
[OperationContract]
[FaultContract(typeof(BusinessFault))]
double Divide(double x, double y);
}
其中BusinessFault是我们自己定义个一个错误类型:
[DataContract]
public class BusinessFault
{
private string _message;
private IDictionary _data;
public BusinessFault()
{
}
[DataMember]
public string Message
{
get
{
return _message;
}
set
{
_message = value;
}
}
[DataMember]
public IDictionary Data
{
get
{
return _data;
}
set
{
_data = value;
}
}
}
注意:BusinessFault类型有一个无参数的构造函数,这个是EHAB需要的。另外BusinessFault类型还有一个Data属性,这个是因为一会我们要把一个异常转换成BusinessFault,这个Data属性就是为了Map异常的Data属性。
之所以在WCF服务端和客户端之间传递我们自己定义的BusinessFault类型而不是Exception类型,原因如下:
1. Exception类型没有DataContract和DataMember特性,无法利用WCF序列化
2. Exception类型中有很多关于异常的详细信息,如调用堆栈等,这些信息没有必要从WCF服务端传递到WCF客户端。这些信息应该记录在WCF服务端的log中。
Service
EHAB提供了ExceptionShielding特性和FaultContractExcpetionHandler类型。我们可以把ExceptionShielding特性应用在WCF服务的实现类上,并指定一个异常处理策略的名字:
[ExceptionShielding ("WCF Exception Shielding")]
class MathService : IMath
{
#region IMath 成员
public double Divide(double x, double y)
{
if (y > -0.01 && y < 0.01)
{
DivideByZeroException ex = new DivideByZeroException("The denominator can't be zero!");
ex.Data["Operation"] = "Divide";
throw ex;
}
return x / y;
}
#endregion
}
这样在MathService类中的方法抛出未处理的异常时,EHAB就会用WCF Exception Shielding策略处理这个异常。
FaultContractExcpetionHandler用于把WCF服务实现类抛出的异常转换成我们定义的BusinessFault类型,下面是服务端的配置文件片段:
<exceptionHandling>
<exceptionPolicies>
<add name="WCF Exception Shielding">
<exceptionTypes>
<add type="System.DivideByZeroException, mscorlib, Version=
postHandlingAction="ThrowNewException" name="DivideByZeroException">
<exceptionHandlers>
<add faultContractType="Ray.Wcf.EhabWithWcf.Contracts.BusinessFault, Ray.Wcf.EhabWithWcf.Contracts"
exceptionMessage="" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF.FaultContractExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF, Version=
name="Fault Contract Exception Handler">
<mappings>
<add source="Message" name="Message" />
<add source="Data" name="Data" />
</mappings>
</add>
</exceptionHandlers>
</add>
</exceptionTypes>
</add>
</exceptionPolicies>
</exceptionHandling>
我们注意到在WCF Exception Shielding异常处理策略下的DivideByZeroException类型中我们用到了FaultContractExceptionHandler。并且我们在mappings节点中指定了DivideByZeroException和BusinessFault属性的Map关系,其中source是DivideByZeroException的属性,name是BusinessFault的属性。
Client
以上WCF服务端代码会在WCF客户端抛出类型为FaultException<BusinessFault>的异常,现在我们想在WCF客户端也用EHAB统一处理异常,以下是WCF客户端的代码:
static void
{
while (true)
{
IMath mathProxy = null;
try
{
ChannelFactory<IMath> factory = new ChannelFactory<IMath>("math");
mathProxy = factory.CreateChannel();
double x = 4;
double y = 0;
Console.WriteLine("Press <Enter> to call Math Service.");
Console.ReadLine();
double result = mathProxy.Divide(x, y);
Console.WriteLine("{0} / {1} = {2}", x, y, result);
Console.ReadLine();
}
catch (Exception ex)
{
if (ExceptionPolicy.HandleException(ex, "UI Exception Policy")) throw;
}
finally
{
IChannel channel = mathProxy as IChannel;
if (null != channel)
{
if (channel.State == CommunicationState.Opened)
{
channel.Close();
}
else
{
channel.Abort();
}
}
}
Console.ReadLine();
}
}
(关于这个片段中关闭通道的代码我很想有人能指点一下,原先我只是简单的close掉通道,但如果通道已损坏就会抛通道处于Fault状态异常,所以改成这个样子了。)
我们注意到这段代码中用UI Exception Policy处理异常,以下是配置文件中的相关片段:
<exceptionHandling>
<exceptionPolicies>
<add name="UI Exception Policy">
<exceptionTypes>
<add type="System.ServiceModel.FaultException`1[[Ray.Wcf.EhabWithWcf.Contracts.BusinessFault, Ray.Wcf.EhabWithWcf.Contracts]], System.ServiceModel, Version=
postHandlingAction="None" name="FaultException`1">
<exceptionHandlers>
<add type="Ray.Wcf.EhabWithWcf.Client.UIExceptionHandler, Ray.Wcf.EhabWithWcf.Client"
name="Custom Handler" />
</exceptionHandlers>
</add>
</exceptionTypes>
</add>
</exceptionPolicies>
</exceptionHandling>
这个片段中值得注意的是泛型异常类型的表示“System.ServiceModel.FaultException`1[[Ray.Wcf.EhabWithWcf.Contracts.BusinessFault, Ray.Wcf.EhabWithWcf.Contracts]], System.ServiceModel, Version=
UIExceptionHandler的实现非常简单,它只是用于在控制台打出异常信息:
[ConfigurationElementType(typeof(CustomHandlerData))]
class UIExceptionHandler : IExceptionHandler
{
public UIExceptionHandler(NameValueCollection ignore)
{
}
#region IExceptionHandler 成员
public Exception HandleException(Exception exception, Guid handlingInstanceId)
{
FaultException<BusinessFault> ex = exception as FaultException<BusinessFault>;
if (null != ex)
{
Console.WriteLine("A business fault is caught.");
Console.WriteLine("\tType: {0}", ex.GetType().AssemblyQualifiedName);
Console.WriteLine("\tMessage: {0}", ex.Detail.Message);
Console.WriteLine("\tOperation: {0}", ex.Detail.Data["Operation"]);
}
return exception;
}
#endregion
}
这个片段中值得注意的地方是应用于UIExceptionHandler上的特性和UIExceptionHandler的构造函数(没用也要有这个构造函数)。
posted on 2007-10-05 16:54 Ray_bihpgh20 阅读(4950) 评论(5) 编辑 收藏 举报