本文的主要结构为:1)WCF错误的概述2)WCF错误契约、错误处理、调试错误、 错误处理扩展、Logbook服务概述3)实现代码分析及运行结果4)源码下载5)下一篇计划6)参考说明
(1)WCF错误的概述(what)
1.1每个面向服务的设计都遵循以下4个院子(经常被称为4原则):边界清晰,服务自治,契约共享,基于策略的兼容性。
1.2异常与异常处理机制是与特定的技术紧密结合的,不能够跨越服务边界。
1.3设计良好的服务应尽可能是自治的,不能依赖于客户端去处理或恢复错误。
1.4任何非空的错误通知都应该是客户端与服务之间契约交互的一部分。
1.5当客户端试图调用服务时,实际上可能会遭遇三种错误类型:
1.第一种错误类型为通信错误,例如网络故障、地址错误、宿主进程没有运行等。
2.第二种错误类型与代理和通道的状态有关,例如试图访问已经关闭的代理,就会导致ObjectDisposedException异常。或者契约与绑定的安全保护级别不相匹配,也会出现错误。
3.第三种错误类型源于服务调用。这种错误既可能是服务抛出的异常,也可能是服务在调用其他对象或资源时,通过内部调用抛出的异常。
1.6在默认情况下,所有服务端抛出的异常总是以FaultException类型到达客户端:
public class FaultException : CommunicationException
{...}
(2)WCF错误契约、错误处理、调试错误、 错误处理扩展、Logbook服务概述
2.1WCF错误契约
1.在默认情况下,服务抛出的异常均以FaultException类型传递到客户端。
2.原因在于任何服务希望与客户端共享的基于通信错误之上的任何异常,都必须属于服务契约行为的一部分。为此,WCF提供了错误契约,通过它列出服务能够抛出的错误类型。这些错误类型应该与FaultException<T>使用的类型参数的类型相同。只要它们在错误契约中列出,WCF客户端就能够分辨契约错误与其他错误之间的区别。
3.FaultContract特性只对标记了它的方法有效。只有这样的方法才能抛出错误,并将它传递给客户端。此外,如果操作抛出的异常没有包含在契约中,则以普通的FaultException形式传递给客户端。为了传递异常,服务必须抛出与错误契约所列完全相同的细节类型。
例如,若要满足如下的错误契约定义:
[FaultContract(typeof(DivideByZeroException))]服务必须抛出FaultException<DivideByZeroException>异常。服务甚至不能抛出错误契约的细节类型的子类,因为它要求异常要满足契约的定义:
[ServiceContract]
interface IMyContract
{
[OperationContract]
[FaultContract(typeof(Exception))]
void MyMethod();
}
class MyService : IMyContract
{
public void MyMethod()
{
//不满足契约的要求
throw new FaultException<DivideByZeroException>(new DivideByZeroException());
}
}
4.FaultContract特性支持重复配置,可以在单个操作中列出多个错误契约:
[ServiceContract]
interface ICalculator
{
[OperationContract]
[FaultContract(typeof(InvalidOperationException))]
[FaultContract(typeof(string))]
double Add(double number1,double number2);
[OperationContract]
[FaultContract(typeof(DivideByZeroException))]
double Divide(double number1,double number2);
//更多方法
}如上的代码允许服务抛出契约定义中的任何一种异常,并将它们传递给客户端。
5.我们不能为单向操作提供错误契约,因为从理论上讲,单向操作是没有返回值的:
//无效定义
[ServiceContract]
interface IMyContract
{
[OperationContract(IsOneWay = true)]
[FaultContract(...)]
void MyMethod();
}如果这样做,就会在装载服务时引发InvalidOperationException异常。
2.2WCF错误处理
1.错误契约与其他服务元数据一同发布。当WCF客户端导入该元数据时,契约定义包含了错误契约,以及错误细节类型的定义。错误细节类型的定义包含了相关的数据契约。如果细节类型是某个包含了各种专门字段的定制异常,那么错误细节类型对数据契约的支持就显得格外重要了。
2.客户端可以采用处理FaultException基类异常的方式,统一地处理所有与通信无关的服务端异常:
CalculatorClient proxy = new CalculatorClient();
try
{
proxy.Divide(2,0);
proxy.Close();
}
catch(FaultException exception)
{...}
catch(CommunicationException exception)
{...}
3.注意:当客户端的开发者通过在客户端移除错误契约,手动修改导入契约的定义时,情况就变得复杂了。此时,如果服务抛出的异常是在服务端错误契约的列表之中,该异常在客户端会被表示为FaultException,而不是契约错误。当服务抛出的异常属于服务端错误契约中列举的异常时,异常不会导致通信通道出现错误。客户端能够捕获该异常,继续使用代理,或者安全地关闭代理。
4.FaultException是一种特殊的异常类型,称之为未知错误(Unknown Fault)。一个未知错误以FaultException类型传递到客户端。它不会使通信通道发生错误,因此客户端能够继续使用代理,就好像该异常属于错误契约中的一部分那样。
2.3WCF调试错误
1.如果服务已经部署,那么最佳方案就是解除该服务与调用它的客户端之间的耦合,在服务的错误契约中,声明最少的异常类型,提供最少的错误信息。但如果是在测试与调试期间,用途更大的却是在返回给客户端的信息中包含所有的异常。它可以使得开发者使用一个测试客户端与调试器分析错误源,而不必处理完全封装的不透明的FaultException。为此,我们应使用ExceptionDetail类。
2.需要创建一个ExceptionDetail实例,然后通过要传递给客户端的异常对它进行初始化。接着,我们将ExceptionDetail的实例作为构造参数,同时提供一个最初的异常消息作为错误原因,抛出一个FaultException<ExceptionDetail>异常对象,而不能抛出不规则的异常。
2.4 WCF错误处理扩展
1. WCF允许开发者定制默认的异常报告与异常传递,它甚至为定制日志提供了一个钩子(hook)。每个通道分发器都支持这种可扩展性,虽然在大多数情况下,我们可能只是简单地利用分发器的可扩展性。
2.若要安装自己的错误处理扩展,需要提供实现了IErrorHandler接口的分发器。IErrorHandler接口的定义如下:
public interface IErrorHandler
{
bool HandleError(Exception error);
void ProvideFault(Exception error,MessageVersion version,ref Message fault);
}虽然说任意一个参与方都可以提供这样的实现,但通常是由服务自身或通过宿主提供接口的实现。实际上,我们能够提供多个错误处理扩展,并将其链接在一起。
2.5Logbook服务
1.一个独立的服务LogbookService,专门用于记录错误日志。
2.使用编写的ErrorHandlerHelper静态类的LogError()方法,可以自动调用LogbookService记录错误日志。
3.除了原来的异常信息,LogError()方法还扩展了异常的解析功能,以及其他记录错误与相关信息的环境变量。
特别的,LogError()方法能够捕获如下信息:
.异常产生的位置(机器名和宿主进程名)。
.异常产生的代码行(程序集名、文件名和行号)。
.抛出异常的类型以及被访问的成员。
.异常的日期和时间。
.异常名和异常消息。
4.LogError()方法的实现与WCF无关,但是,它的实现大量地运用了.NET编程技术,例如字符串与异常解析、获取环境信息等。错误信息通过专门的数据契约传递给LogbookService服务。
(3)实现代码分析及运行结果
3.1WCFService(错误契约源码分析)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; namespace WCFService { //服务契约 [ServiceContract] public interface IMyContract { //操作契约 [OperationContract] //错误契约 [FaultContract(typeof(DivideByZeroException))] void MyMethodWithError(); //操作契约 [OperationContract] void MyMethodWithoutError(); } //实现服务契约定义的方法 public class MyService : IMyContract,IDisposable { public void MyMethodWithError() { Console.WriteLine("MyMethodWithError()"); DivideByZeroException exception = new DivideByZeroException("Do not divide 0"); throw new FaultException<DivideByZeroException>(exception); } public void MyMethodWithoutError() { Console.WriteLine("MyMethodWithoutError()"); } public void Dispose() { Console.WriteLine("Dispose"); } } }
3.2WCFHost(宿主源码分析)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; namespace WCFHost { class Program { static void Main(string[] args) { ServiceHost host = new ServiceHost(typeof(WCFService.MyService), new Uri("http://localhost:8000/")); //判断是否以及打开连接,如果尚未打开,就打开侦听端口 if (host.State != CommunicationState.Opened) { host.Open(); } //显示运行状态 Console.WriteLine("host is running,the state is {0}", host.State); //For Debug Console.ReadKey(); host.Close(); } } }
运行结果见图6-1服务器运行结果
3.3WCFClient(客户端源码分析)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; namespace WCFClient { class Program { static void Main(string[] args) { MyContractClient proxy = new MyContractClient(); //通过代理调用MyService服务 try { proxy.MyMethodWithError(); } catch (FaultException<DivideByZeroException> exception) { Console.WriteLine("First:" + exception.GetType() + "/r/n" + exception.Message); } catch (FaultException exception) { Console.WriteLine("First:" + exception.GetType() + "/r/n" + exception.Message); } catch (CommunicationException exception) { Console.WriteLine("First:" + exception.GetType() + "/r/n" + exception.Message); } catch (Exception exception) { Console.WriteLine("First:" + exception.GetType() + "/r/n" + exception.Message); } try { proxy.MyMethodWithoutError(); } catch (Exception exception) { Console.WriteLine("Second:" + exception.GetType() + "/r/n" + exception.Message); } //For Debug Console.WriteLine("press any key!"); Console.ReadKey(); } } }
运行结果见图6-2客户端运行结果
(4)源码下载
http://download.csdn.net/source/3026446
(5)下一篇计划
下一篇主要介绍WCF中事务的相关知识。
(6)参考说明
1.《Programming WCF Services》