不管是哪种服务操作都可能在某些时候出现异常,如果处理异常时我们需要关心的问题,良好的编程方式应该是自制的,服务器应该在错误发生时捕获它并进行相应处理,异常不应该依靠客户端来进行处理,任何依靠客户端的处理机制均会导致服务器客户端的紧耦合。
一 错误隔离
在WCF的异常处理模型中,如果代表某个客户端的服务导致异常,并不会结束宿主进程,其他客户端仍能访问该服务,托管在相同进程中的其他服务也不会受到影响。因此,当一个未经处理的异常离开服务范围时,分发器会捕捉它,并将其序列化后传递给客户端,当消息到达客户端代理时,代理会在客户端抛出异常。这种方式为每个WCF服务提供了进程级的隔离。客户端与服务能够共享一个进程,但对于错误而言是完全独立的。唯一例外是导致.net崩溃的关键错误。错误隔离室WCF三个关键错误解耦特性中的一个,其他两个是错误屏蔽与通道故障。
二 错误屏蔽
在客户端试图访问服务时,可能遇到三种错误。第一种是通信错误,如网络故障,地址错误,宿主进程没有运行等。客户端错误表现为CommunicationExcaption或其子类。第二种错误是与代理和通道状态相关。包括试图访问已关闭的代理的ObjectDisposedExcaption,契约与绑定的安全保护级别不匹配的InvalidOperationException等。第三种异常源于服务调用。这种错误既可能是服务抛出的异常,也可能是服务在调用其他对象或资源时,通过内部调用抛出的异常。
客户端应该关注的就是错误发生了,而不是错误的细节。让客户端不至于突然关闭,对于这样的问题WCF总是让错误以FaultException的形式传递到客户端。这样的目的任然是为了松耦合。
三 通道故障
在传统的.Net编程中错误处理可能是这样的。
try { obj.DoMethod(); } catch (Exception) { //处理异常 } obj.DoMethod();
然而这样的错误处理方式是存在问题的。如果DoMethod方法抛出异常说明这个方法在当前上下文上是存在问题的。但是在客户端处理过异常后任然可以继续调用。在WCF为我们解决了这一问题。在具有传输会话的服务会话中,如果出现未处理的异常就会使通道发生错误。这样就避免客户端继续使用该代理。
四 错误传播
如果服务需要将特定的错误传到客户端然而错误又不能跨越服务边界。对于这样的问题就需要将特定技术的异常映射为某种与平台无关的错误信息。这种表现形式就是所谓的SOAP错误。SOAP错误基于一种行业标准,它不依赖于任何一种诸如CLR异常、Java异常或C++异常之类的特定技术的异常。若要返回一个SOAP错误,服务就不能抛出一个传统的CLR异常。相反,服务必须抛出FaultException<T>类的实例。FaultException<T>是FaultException的特化。
五 错误契约
默认情况下,服务抛出的异常总是以FaultException类型进行传递,即使在服务器端抛出FaultException<T>也是如此,如果需要使得该异常能够穿透错误屏蔽使得客户端能够共享到详细的错误信息。就必须将错误作为契约的一部分告知客户端。
/*******************************契约*********************************/
[ServiceContract]
public interface IMember
{
......
[OperationContract]
[FaultContract(typeof(DivideByZeroException))]
[FaultContract(typeof(InvalidDataException))]
double Divide(double num1, double num2);
}
/******************************服务实现******************************/
public double Divide(double num1, double num2)
{
if (num1 == 0)
{
throw new FaultException<InvalidDataException>(
new InvalidDataException("被除数必须非0"));
}
if (num2 == 0)
{
throw new FaultException<DivideByZeroException>(
new DivideByZeroException("试图除以0"));
}
return num1 / num2;
}
/*******************************客户端*******************************/
try
{
client.Divide(0, 100);
}
catch (FaultException<DivideByZeroException> e)
{
Console.WriteLine(e.Detail.ToString());//Output 试图除以0
}
catch (FaultException<InvalidDataException> e)
{
Console.WriteLine(e.Detail.ToString());//Output 被除数必须非0
}
Console.WriteLine(client.Add(new int[] { 1, 2, 3 })); //Output 6
这里需要注意的是,错误契约定义的错误类型必须和服务抛出的FaultException<T>的特化类型相同,客户端捕获的类型必须和服务服务抛出的类型相同。
回调操作的错误处理使用同样的机制。