WCF初探-20:WCF错误协定
WCF错误协定概述
- 在所有托管应用程序中,处理错误由 Exception 对象表示。 在基于 SOAP 的应用程序(如 WCF 应用程序)中,服务方法使用 SOAP 错误消息来传递处理错误信息。 SOAP 错误是包括在服务操作元数据中的消息类型,因此会创建一个错误协定,客户端可使用该协定来使操作更加可靠或更具交互性。 此外,由于 SOAP 错误在客户端以 XML 格式表示,这是一种任何 SOAP 平台上的客户端都可以使用的具有极好的互操作性的类型系统,可增加 WCF 应用程序的适用范围。
- 由于 WCF 应用程序在两种类型的错误系统下都可运行,因此发送到客户端的任何托管异常信息都必须在服务上从异常转换为 SOAP 错误,并在 WCF 客户端中从 SOAP 错误转换为错误异常。 对于双工客户端,客户端协定还可以将 SOAP 错误发送回服务。 在任一种情况下,您都可以使用默认的服务异常行为,或者可以显式控制是否(以及如何)将异常映射到错误消息。
- 可以发送两种类型的 SOAP 错误:已声明的和未声明的。 已声明的 SOAP 错误是指其中的某个操作具有 System.ServiceModel.FaultContractAttribute 属性(用于指定自定义 SOAP 错误类型)的错误。 未声明的 SOAP 错误是在操作的协定中没有指定的错误。
创建错误协定
- 要实现错误协定传递到客户端,首先需要将将 ServiceBehaviorAttribute.IncludeExceptionDetailInFaults 或 ServiceDebugBehavior.IncludeExceptionDetailInFaults 设置为 true(可以在服务配置文件中指定)。
- 定义错误协定时,我们通常是自定义错误信息显示。通常我们将需要显示的错误信息定义为数据协定,再使用FaultContractAttribute在操作协定上进行标记类型为哪一种数据协定的错误。
- 操作过程中发生托管异常时将引发 FaultException<TDetail>(此处类型参数为可序列化的错误信息)。 WCF 客户端应用程序呈现的 SOAP 错误类型与客户端实现中引发的类型相同,即FaultException<TDetail>(此处类型参数为可序列化的错误信息)。 FaultContractAttribute 只能用来为双向服务操作和异步操作对指定 SOAP 错误;单向操作并不支持 SOAP 错误,因而不支持 FaultContractAttribute。
WCF错误协定示例
- 解决方法结构如下:
- 工程结构说明:
- Service:类库程序,WCF服务端程序。在服务协定接口ISampleService.cs中定义了数据协定FaultMessage来显示错误的时间和信息,在操作协定SampleMethod上,我们用FaultContract来标记错误协定的类型为FaultMessage。在实现操作协定方法SampleMethod中,我们可以利用FaultException<FaultMessage>来自定义错误,抛出自定义异常信息。在客户端利用FaultException<FaultMessage>中的Detail可以获取包含详细错误信息的对象。ISampleService.cs的代码如下:
using System.ServiceModel; using System.Runtime.Serialization; namespace Service { [ServiceContract] public interface ISampleService { [OperationContract] [FaultContract(typeof(FaultMessage))] string SampleMethod(string msg); } [DataContract] public class FaultMessage { private string _errorTime; private string _errorMessage; [DataMember] public string ErrorTime { get { return this._errorTime; } set { this._errorTime = value; } } [DataMember] public string ErrorMessage { get { return this._errorMessage; } set { this._errorMessage = value; } } public FaultMessage(string time,string message) { this._errorTime = time; this._errorMessage = message; } } }
SampleService.cs的代码如下:
using System.ServiceModel; namespace Service { public class SampleService :ISampleService { public string SampleMethod(string msg) { if (msg.Length < 10) { return "输入的字符串为" + msg; } else { throw new FaultException<FaultMessage>( new FaultMessage("错误时间:" + System.DateTime.Now.ToString(), "输入的【" + msg + "】字符串大于10个字符")); } } } }
2. Host:控制台应用程序,服务承载程序。添加对程序集Service的引用,完成以下代码,寄宿服务。Program.cs代码如下:
using System; using System.ServiceModel; using Service; namespace Host { class Program { static void Main(string[] args) { using (ServiceHost host = new ServiceHost(typeof(SampleService))) { host.Opened += delegate { Console.WriteLine("服务已经启动,按任意键终止!"); }; host.Open(); Console.Read(); } } } }
App.config代码如下:
<?xml version="1.0"?> <configuration> <system.serviceModel> <services> <service name="Service.SampleService" behaviorConfiguration="mexBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:1234/SampleService/"/> </baseAddresses> </host> <endpoint address="" binding="wsHttpBinding" contract="Service.ISampleService" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="mexBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
我们通过svcutil.exe工具生成客户端代理类和客户端的配置文件
svcutil.exe是一个命令行工具,位于路径C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin下,我们可以通过命令行运行该工具生成客户端代理类
- 在运行中输入cmd打开命令行,输入 cd C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin
- 输入svcutil.exe /out:f:\ SampleServiceClient.cs /config:f:\App.config http://localhost:1234/ SampleService
3. Client:控制台应用程序,客户端调用程序。将生成的UserInfoClient.cs和App.config复制到Client的工程目录下,完成客户端调用代码。Program.cs的代码如下:
using System; using System.ServiceModel; using Service; namespace Client { class Program { static void Main(string[] args) { SampleServiceClient proxy = new SampleServiceClient(); try { Console.WriteLine(proxy.SampleMethod("WCF")); Console.WriteLine(proxy.SampleMethod("Windows Communication Foundation")); } catch (FaultException<FaultMessage> ex) { Console.WriteLine(ex.Detail.ErrorTime+"\n"+ex.Detail.ErrorMessage); } finally { Console.Read(); } } } }
程序运行结果如下:
总结
- 通常我们在服务端通过FaultException抛出异常信息,然后就可以在客户端进行处理,即使我们不加FaultContract特性。参照:WCF初探-12:WCF客户端异常处理
- 我们也可以通过添加ServiceBehavior特性,将服务的IncludeExceptionDetailInFaults设置为true(默认为 false),客户端也可以捕获抛出的非FaultException异常信息,但该异常仍然会导致通道出现错误。
- 在本示例中,我们实现了错误协定的自定义错误信息操作。其中最重要的FaultException<TDetail>,通过TDetail类型,我们可以把自定义的数据协定错误信息在服务端客户端进行序列化和反序列化,最终实现SOAP消息的错误信息的传递。