一、错误传播

服务需要向客户端报告特定错误,当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);
</span><span style="color: #008000">//</span><span style="color: #008000"> 返回 System.ServiceModel.Channels.MessageFault 对象。</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">virtual</span><span style="color: #000000"> MessageFault CreateMessageFault();
</span><span style="color: #008000">//</span><span style="color: #008000"> 更多成员</span>

}

// 用于在客户端应用程序中捕获通过协定方式指定的 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里捕获所有的通信和服务端异常。

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

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

[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编程,代码也具有更强的可读性。

传递给FaultException<T>构造函数的reason参数为异常消息,因此可以传递字符串作为reason参数的值。

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

二、错误契约

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

服务可以使用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抛出这样的错误,也不会让连接客户端的通道出现故障。

三、错误与回调

由于通信异常或回调自身抛出的异常,到客户端的回调自然就会失败。与服务契约操作类似,回调契约操作同样可以定义错误契约,如下所示:

[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>();
  </span><span style="color: #0000ff">try</span><span style="color: #000000">
{
callback.OnCallBack();
}
</span><span style="color: #0000ff">catch</span>(FaultException&lt;DivideByZeroException&gt; exception) <span style="color: #008000">//</span><span style="color: #008000">客户端依然能够获得异常</span>

{

Trace.WriteLine("Callback threw: " + exception.GetType() + " " + exception.Message);

}

  </span><span style="color: #0000ff">catch</span>(FaultException&lt;<span style="color: #0000ff">int</span>&gt;<span style="color: #000000"> exception)
{
Trace.WriteLine(</span><span style="color: #800000">"</span><span style="color: #800000">Callback threw: </span><span style="color: #800000">"</span> + exception.GetType() + <span style="color: #800000">"</span> <span style="color: #800000">"</span> +<span style="color: #000000"> exception.Message);
}
</span><span style="color: #0000ff">catch</span><span style="color: #000000">(CommunicationException exception)
{
Trace.WriteLine(</span><span style="color: #800000">"</span><span style="color: #800000">Callback threw: </span><span style="color: #800000">"</span> + exception.GetType() + <span style="color: #800000">"</span> <span style="color: #800000">"</span> +<span style="color: #000000"> exception.Message);
}

}

}

[WCF编程]11.错误:错误契约的更多相关文章

  1. [WCF编程]11.错误:错误类型

    一.错误概述 不管是哪一种操作,在任意时刻都可能出现不可预期的错误.问题在于我们应该如何将错误报告给客户端.异常和异常处理机制是与特定技术紧密结合的,不能跨越边界的.此外,如果有客户端来处理错误,必定 ...

  2. [WCF编程]4.契约概述

    一.契约的基本概念 契约是消息参与者之间的约定.在SOA架构中,契约提供了服务通信所必需的元数据.契约用来定义数据类型,操作,消息交换模式和消息交换使用的传输协议.契约通常是在标准化平台中使用与编程语 ...

  3. [转载]WCF 几种常见错误

    WCF标准的配置文件为: <system.serviceModel>         <services>             <service name=" ...

  4. 在 root 下执行 Oracle 程序时找不到 libclntsh.so.11.1 错误的解决办法。

    在 root 下执行 Oracle 程序时找不到 libclntsh.so.11.1 错误的解决办法. 先确定 libclntsh.so.11.1 所在目录: [oracle@localhost ~] ...

  5. Windows核心编程第一章.错误处理

    Windows核心编程第一章,错误处理. 一丶错误处理 1.核心编程学习总结 不管是做逆向,开始做开发.在Windows下.你都需要看一下核心编程这本书.这本书确实写得很好.所以自己在学习这本书的同时 ...

  6. WCF分布式开发常见错误解决(1):An error occurred while attempting to find services at...添加服务引用出错

          WCF分布式开发常见错误解决(1):An error occurred while attempting to find services at...添加服务引用出错   当我们在客户端添 ...

  7. java编程出现的错误对应的解决方法

    error: could not open D:\java\jre1.8\lib\amd64\jvm.cfg 解决方法:把java的环境变量%JAVA_HOME%/bin上移到最上面 优化 查看网页源 ...

  8. .Net-WCF-图书:《WCF编程》

    ylbtech-.Net-WCF-图书:<WCF编程> <WCF编程>是2008年1月机械工业出版社出版的图书,作者是Juval Lowy.Clemens Vasters. 1 ...

  9. WCF 编程实验室

    最近由于项目需要,简单研究了一下.NET WCF编程. 首先,简单说下WCF是什么,WCF 本质上,是一种开发框架.它用来开发类似COM+ .WEB SERVICE 这样“远程方法调用” 功能. 普通 ...

随机推荐

  1. 剑指Offer面试题:10.数值的整数次方

    一.题目:数值的整数次方 题目:实现函数double Power(doublebase, int exponent),求base的exponent次方.不得使用库函数,同时不需要考虑大数问题. 在.N ...

  2. Mac下设置Android源代码编译环境

    在Mac下编译Android最麻烦的就是设置Android的编译环境了,做完这一步基本上剩下的就是近乎傻瓜式的操作了.说起来也简单就三步,设置大小写敏感的文件系统.安装编译工具.设置文件系统同时能打开 ...

  3. 用Powershell启用Windows Azure上的远程桌面服务

    [题外话] 某天不小心点了XX管家的自动修复,虽然及时点了取消也看到了远程桌面服务成功被关闭,但是忙完该干的事以后竟然忘记了这件事,在断开远程桌面服务之前也忘记再次打开.以至于之后几天一直以为Azur ...

  4. Mac下配置Apache服务

    这篇文章主要是针对Mac用户,第一次搭建本地开发环境的同学,已经搭建过的同学可以忽略. Mac自带的Apache还是XAMPP? That is a question. 其实自带的apache也够用了 ...

  5. Modern OpenGL用Shader拾取VBO内单一图元的思路和实现(2)

    Modern OpenGL用Shader拾取VBO内单一图元的思路和实现(2) 上一篇里介绍了Color-Coded Picking的思路和最基本的实现.在处理GL_POINTS时已经没有问题,但是处 ...

  6. springboot之HelloWorld

    简介 为了简化开发Spring的复杂度,Spring提供了SpringBoot可以快速开发一个应用,这里就简单介绍下SpringBoot如何快速开发一个J2EE应用 HelloWorld 首先在gra ...

  7. struts.xml的编辑

    <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "- ...

  8. C#设计模式系列:工厂方法模式(Factory Method)

    1. 工厂方法模式简介 1.1 定义 工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法模式是以一个类的实例化延迟到其子类. Factory Method模式用于在不指定待创建 ...

  9. C#设计模式系列:开闭原则(Open Close Principle)

    1.开闭原则简介 开闭原则对扩展开放,对修改关闭,开闭原则是面向对象设计中可复用设计的基石. 2.开闭原则的实现 实现开闭原则的关键就在于抽象,把系统的所有可能的行为抽象成一个抽象底层,这个抽象底层规 ...

  10. C#中的readonly与const的比较

    C#中有两种常量类型,分别为readonly(运行时常量)与const(编译时常量),本文将就这两种类型的不同特性进行比较并说明各自的适用场景.工作原理 readonly为运行时常量,程序运行时进行赋 ...