WCF 4 Cookbook 系列(一) 使用契约 [下]
在这篇文章中,将会包括:
- 定义一个one-way契约
- 使DataContract向前兼容
- 从XML Schema生成DataContract
- 使用XMLSerializer控制消息序列化方式
- 使用MessageContract控制SOAP消息
- 通过Contract添加一个自定义SoapHeader
- 通过FaultContract返回自定义异常数据
通过Contract添加一个自定义SoapHeader
SOAP消息(被一个XML Web 服务和WCF 服务使用)会包含一个以Envelope标签为根的标准XML文档,这个标签内依次由一个请求体(required Body)元素和一个可选头(Header)元素组成。可选头下的每个子元素被称为一个SoapHeader,起着和其他头元素类似的作用,采用一定的网络协议的发送包。
一个SoapHeader常被用在SOAP消息中,除了SOAP消息主体(body)外,携带一些应用协议级数据。WCF已经为它支持的一些协议(WS-Security, WS-Reliability等等)使用了许多内置的SoapHeader。对于一些用户场景,我们也会需要向WCF服务消息添加一个自定义的SoapHeader,以便交换额外的信息(大部分时候为了通信目的)。
怎么做…
- 我们需要定义一个自定义类型表示SoapHeader用来被序列化进服务消息。下面的示例DataContract类型展示的是一个被用来自定义身份验证的自定义头:
[DataContract] public class MyUsernameToken { [DataMember] public string Username { get; set; } [DataMember] public string Password { get; set; } }
- 接下来,在服务操作的MessageContract中应用自定义头类型。这时候我们应该用MessageHeaderAttribute来标记MessageContract成员。
[MessageContract] public class HelloRequest { [MessageHeader] public MyUsernameToken AuthUser { get; set; } [MessageBodyMember] public string User { get; set; } }
- 最后,我们需要使用MessageContract类型作为特定的服务操作的唯一输入参数/返回值。
[MessageContract] public class HelloResponse { [MessageBodyMember] public string Reply { get; set; } }
原理…
MessageHeaderAttribute帮助标记(MessageContract类型的)特定类型的成员为SoapHeader,这样就被嵌入在SOAP信封(SOAP Envelope)。另外,从头(header)在设计时被添加进MessageContract,WCF自动生成的元数据就会包含SoapHeader信息,见下面的截图:
如果使用Visual Studio 或者Svcutil.exe 生成客户端代理类,生成的代理类型会自动映射SoapHeader到操作参数。因此,当调用服务操作的时候,可以简单地通过SoapHeader数据作为操作参数。下面的代码演示了如何使用自定义SoapHeader指定自动生成的服务代理调用操作。
private static void CallService() { TestProxy.TestServiceClient client = new TestProxy.TestServiceClient(); TestProxy.MyUsernameToken utoken = new TestProxy.MyUsernameToken{ Username="Foo", Password="Bar"}; string reply = client.SayHello(utoken, "WCF user"); Console.WriteLine(reply); }
通过捕获底层SOAP消息,可以发现MyUsernameToken头类型被序列化为一个在<Header>节里的SoapHeader。
更多…
WCF支持各种方式来向一个服务消息添加自定义SoapHeader。这个部分演示了静态的使用自定义类型和MessageContract添加一个自定义SoapHeader。除此之外,我们也可以使用编码的方式向服务消息添加SoapHeader。
通过FaultContract返回自定义异常数据
在一个XML Web服务和WCF 服务里,服务端可以在返回的消息里用SoapFault返回任何未处理的异常。对于WCF服务操作,异常返回给客户端,通过以下两个步骤:
- 映射异常条件到自定义SOAP错误
- 服务端和客户端发送和接收SOAP错误作为异常
默认情况下,WCF会返回简单错误消息或者完整的异常消息(包括调用栈)作为Fault的内容。然而,有时我们想要封装原始异常信息或者返回以下用户友好的错误到客户端。要支持这种自定义的异常信息格式,WCF提供了FaultContract和FaultException特性。FaultException是一个新的通用异常类型,帮助WCF服务用一种统一的方式封装各种自定义错误数据对象。FaultContract是被正式指定的FaultException类型,将从目标WCF服务操作返回,以便客户端消费者能够正确的处理和解析它。
怎么做…
- 首先,创建将要作为异常消息返回给客户端的自定义错误类型。自定义错误类型作为标准DataContract类型来定义。
- 然后,通过FaultContractAttribute应用自定义错误类型到服务操作(将返回这个自定义错误类型)上。
- 下面的代码展示了一个自定义错误类型和服务操作的示例:
[DataContract] public class UserFriendlyError { [DataMember] public string Message { get; set; } } [ServiceContract] public interface ICalcService { [OperationContract] int Divide(int lv, int rv); [OperationContract] [FaultContract(typeof(UserFriendlyError))] int DivideWithCustomException(int lv, int rv); }
- 最后,我们需要在服务操作的实现中添加异常处理代码,并通过FaultException<T>类型返回自定义错误类型(见下面的DivideWithCustomException方法):
public int Divide(int lv, int rv) { if (rv == 0) throw new Exception("Divided by Zero is not allowed!"); return lv / rv; } public int DivideWithCustomException(int lv, int rv) { if (rv == 0) throw new FaultException<UserFriendlyError>( new UserFriendlyError() { Message = "Divided by Zero is not allowed!" } ); return lv / rv; }
原理…
应用在服务操作上的FaultContractAttribute使得在运行时向WSDL文档中生成相应的元数据(见下面截图)。因此,生成的客户端代理知道怎么处理这个自定义错误类型。
当调用操作的时候,能够添加代码处理特定的FaultException,并从Exception.Detail属性中获得用户友好的错误信息。
try { //call the operation with invalid parameter } catch (FaultException<UserFriendlyError> fex) { Console.WriteLine("Error: {0}", fex.Detail.Message); }
如果使用Fiddler 2或者消息记录查看底层的SOAP消息,我们也可以在XML序列化格式中发现自定义错误数据:
更多…
由于通过FaultContract来使用的自定义错误类型支持任何有效的DataContract类型,我们可以定义各种自定义异常数据内容(从简单的原始类型到复杂嵌套的类)。
关于Fiddler 2的使用教程,最近园友小坦克进行了详细总结,需要阅读请移步:Fiddler 教程。
Posted by Bryce Zhang
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0