[WCF编程]11.错误:错误契约

1、错误传播

    服务须要向客户端报告特定错误,当WCF默认的错误屏蔽方法并不包含这一实现。另外一个重要的问题与传播到客户端有关,即因为异常是针对特定技术的,所以没法跨越服务边界而被共享。要实现无缝的互操做性,就须要将基于特定技术的异常映射为某种与平台无关的错误信息。这种表现形式就是所谓的SOAP错误。编程

    SOAP错误基于一种行业标准,它不依赖任何一种诸如CLR异常、Java异常或者C++异常等的特定技术异常。安全

    若要返回一个SOAP错误,服务就不能抛出一个传统的CLR异常。相反,服务必须抛出FaultException<T>类的实例,以下所示:服务器

// 表示 SOAP 错误。
[Serializable]
public class FaultException : CommunicationException
{
    public FaultException();
    public FaultException(FaultReason reason);
    public FaultException(string reason);

    // 返回 System.ServiceModel.Channels.MessageFault 对象。
    public virtual MessageFault CreateMessageFault();
    // 更多成员
}

// 用于在客户端应用程序中捕获经过协定方式指定的 SOAP 错误。
[Serializable]
public class FaultException<TDetail> : FaultException
{
    public FaultException(TDetail detail);
    public FaultException(TDetail detail, FaultReason reason);
    public FaultException(TDetail detail, string reason);
    // 更多成员
}

    FaultException<T>是FaultException的特殊化,所以任何针对FaultException异常进行编程的客户端都能处理FaultException<T>类型。FaultException继承子CommunicationException,所以,客户端能够在单个catch里捕获全部的通讯和服务端异常。ide

    FaultException<T>的类型参数T负责传递错误细节。不要求错误细节的必须为Exception的派生类,它能够是任何类型。惟一的约束是该类型必须标记为可序列化或数据契约。函数

    以下代码,展现了一个简单的计算器服务,在实现Divide()方法,若是除数为0,则抛出一个FaultException<DivideByZeroException>异常。spa

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   double Divide(double number1,double number2);
}

class MyService : IMyContract
{
   public double Divide(double number1,double number2)
   {
      if(number2 == 0)
      {
          DivideByZeroException exception = new DivideByZeroException();
          throw new FaultException<DivideByZeroException>(exception);
      }
      return number1 / number2
   }
}

    除了FaultException<DivideByZeroException>异常,服务还可以抛出参数不能Exception派生类型的异常。代理

throw new FaultException<double>(number2);

    将Exception派生类做为错误细节类型更加符合传统.NET编程,代码也具备更强的可读性。code

    传递给FaultException<T>构造函数的reason参数为异常消息,所以能够传递字符串做为reason参数的值。对象

DivideByZeroException exception = new DivideByZeroException("number2 is 0"); 
throw new FaultException<DivideByZeroException>(exception,"Reason:" + exception.Message);

2、错误契约

    默认状况下,服务抛出传递到客户端的异常均为FaultException类型,即便在服务器抛出FaultException<T>的状况下也是如此。其缘由在与服务但愿与客户端共享的基于错误之上的任何异常都必须属于服务契约行为的一部分,从而使得服务可以通知WCF它但愿可以穿透错误的屏蔽。为此,WCF提供了错误契约,经过它能够列出服务可以抛出的错误类型。这些错误类型的类型参数应该与FaultException<T>使用的类型参数相同。只是他们在错误契约中列出,WCF客户端就可以分辨契约错误与其它错误之间的区别。blog

    服务可使用FaultContractAttribute特性定义它的错误契约:

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   [FaultContract(typeof(DivideByZeroException))]
   double Divide(double number1,double number2);

}

    FaultContract特性只对标记了它的方法有效。也就是说,只有这样的方法才能抛出错误,并将它传递给客户端。

    此外,若是操做抛出的异常没有包含在契约中,它就会以普通的FaultException形式传递给客户端。为了传递异常,服务抛出与错误契约所列彻底相同的细节类型。例如,若要抛出以下的错误契约定义:

[FaultContract(typeof(DivideByZeroException))]

    服务就必须抛出一个FaultException<DivideByZeroException>异常,即便是父类--子类也不行,必须是彻底相同的类型。

    FaultContract特性支持重复配置,能够在单个操做中列出多个契约。

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   [FaultContract(typeof(DivideByZeroException))]
   [FaultContract(typeof(InvalidOpreationException))]
   double Divide(double number1,double number2);
}

    咱们不能为单向操做提供错误契约。由于,从理论上讲,单向操做是没有返回值的。

错误处理

    错误契约与其它服务元数据一同发布。当WCF客户端导入该元数据时,契约定义包含了错误契约及错误细节类型的定义。错误细节类型的定义包含了相关的数据契约。若是细节类型是某个包含了各类专门字段的定制异常,那么错误细节类型对数据契约的支持就显得格外重要了。

    客户端指望可以捕获和导入的错误类型。以下所示:

MyContractClient proxy = new MyContractClient();
try
{
    proxy.Divide(2,0);
    proxy.Close();
}
catch(FaultException<DivideByZeroException> exception)
{  ....  }
catch(FaultException exception)
{ ...... }
catch(Exception exception)
{ ...... }

    注意,客户端仍然可能引起通讯异常或者服务抛出的其它异常。

    客户端能够采用FaultException基类异常的方式赞成地处理全部与通讯无关的全部异常。

MyContractClient proxy = new MyContractClient();
try
{
    proxy.Divide(2,0);
    proxy.Close();
}
catch(FaultException exception)
{  ....  }
catch(CommunicationException exception)
{ ...... }

错误与通道

    若是要作到能够预料错误,则须要在错误契约中列出一个能够抛出的错误。所以,当服务器抛出的错误属于服务错误契约中列出的异常时,就不会致使通讯通道出现错误。客户端可以捕获该异常,继续使用代理或者安全的关闭代理。这就容许服务类将常规异常和列举在错误契约中的错误区别对待,让这些错误不会致使通道出现故障。这一能力不限于服务,若是服务调用的任意下游的.NET抛出这样的错误,也不会让链接客户端的通道出现故障。

3、错误与回调

    因为通讯异常或回调自身抛出的异常,到客户端的回调天然就会失败。与服务契约操做相似,回调契约操做一样能够定义错误契约,以下所示:

[ServiceContract(CallbackContract = typeof(IMyContractCallback))] 
interface IMyContract
{
   [OperationContract] 
   void DoSomething();
}
interface IMyContractCallback
{
   [OperationContract]
   [FaultContract(typeof(int))]
   [FaultContract(typeof(DivideByZeroException))]
   void OnCallBack();
}

    当服务在一个服务操做中调用回调时,若是返回到它正在调用的客户端,且异常既不是FaultException也不是它的子类,则状况就变得异常复杂了。因为双向通讯的全部绑定都维持了一个传输会话层,所以在回调期间,异常会终止从客户端到服务的传输。

    因为TCP和IPC绑定不管从客户端到服务发起的调用,仍是服务到客户端的回调,都使用了相同的传输,一旦回调抛出异常,则第一个调用服务的客户端即便服务已经捕获了异常也会即刻收到一个CommunicationException异常。这就是在两个方向重用了相同传输带来的后果,致使回调传输出现错误(等同于让客户端到服务传输出现错误)。服务可以捕获和处理异常,可是客户端仍然会得到它的异常:

[ServiceContract(CallbackContract = typeof(IMyContractCallback))] 
interface IMyContract
{
   [OperationContract] 
   void DoSomething();
}
interface IMyContractCallback
{
   [OperationContract]
   [FaultContract(typeof(int))]
   [FaultContract(typeof(DivideByZeroException))]
   void OnCallBack();
}

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
class MyService : IMyContract
{
   public void DoSomething()
   {
      IMyContractCallback callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();

      try
      {
         callback.OnCallBack();
      }
      catch(FaultException<DivideByZeroException> exception) //客户端依然可以得到异常
      {
         Trace.WriteLine("Callback threw: " + exception.GetType() + " " + exception.Message);
      }

      catch(FaultException<int> exception)
      {
         Trace.WriteLine("Callback threw: " + exception.GetType() + " " + exception.Message);
      }
      catch(CommunicationException exception)
      {
         Trace.WriteLine("Callback threw: " + exception.GetType() + " " + exception.Message);
      }
   }
}
相关文章
相关标签/搜索