WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化

Posted 260250932

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化相关的知识,希望对你有一定的参考价值。

在本篇文章中,我们将讨论WCF四大契约(服务契约、数据契约、消息契约和错误契约)之一的消息契约(Message Contract)。服务契约关注于对服务操作的描述,数据契约关注于对于数据结构和格式的描述,而消息契约关注的是类型成员与消息元素的匹配关系。

我们知道只有可序列化的对象才能通过服务调用在客户端和服务端之间进行传递。到目前为止,我们知道的可序列化类型有两种:一种是应用了System.SerializableAttribute特性或者实现了System.Runtime.Serialization.ISerializable接口的类型;另一种是数据契约对象。对于基于这两种类型的服务操作,客户端通过System.ServiceModel.Dispatcher.IClientMessageFormatter将输入参数格式化成请求消息,输入参数全部内容作为有效负载置于消息的主体中;同样地,服务操作的执行结果被System.ServiceModel.Dispatcher.IDispatchMessageFormatter序列化后作为回复消息的主体。

在一些情况下,具有这样的要求:当序列化一个对象并生成消息的时候,希望将部分数据成员作为SOAP的报头,部分作为消息的主体。比如说,我们有一个服务操作采用流的方式进行文件的上载,除了以流的方式传输以二进制表示的文件内容外,还需要传输一个额外的基于文件属性的信息,比如文件格式、文件大小等。一般的做法是将传输文件内容的流作为SOAP的主体,将其属性内容作为SOAP的报头进行传递。这样的功能,可以通过定义消息契约来实现。

一、 消息契约的定义

消息契约和数据契约一样,都是定义在数据(而不是功能)类型上。不过数据契约旨在定义数据的结构(将数据类型与XSD进行匹配),而消息契约则更多地关注于数据的成员具体在SOAP消息中的表示。消息契约通过以下3个特性进行定义:System.ServiceModel.MessageContractAttributeSystem.ServiceModel.MessageHeaderAttributeSystem.ServiceModel.MessageBodyMemberAttributeMessageContractAttribute应用于类型上,MessageHeaderAttributeMessageBodyMemberAttribute则应用于属性或者字段成员上,表明相应的数据成员是一个基于SOAP报头的成员还是SOAP主体的成员。先来简单介绍一下这3个特性:

1MessageContractAttribute

通过在一个类或者结构(Struct)上应用MessageContractAttribute使之成为一个消息契约。从MessageContractAttribute的定义来看,MessageContractAttribute大体上具有以下两种类型的属性成员:

1: [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = false)]

2: public sealed class MessageContractAttribute : Attribute

3: {

4: //其他成员

5: public bool HasProtectionLevel { get; }

6: public ProtectionLevel ProtectionLevel { get; set; }

7:?

8: public bool IsWrapped { get; set; }

9: public string WrapperName { get; set; }

10: public string WrapperNamespace { get; set; }

11: }

下面的代码中将Customer类型通过应用MessageContractAttribute使之成为一个消息契约。IDName属性通过应用MessageHeaderAttribute定义成消息报头(Header)成员,而Address属性则通过MessageBodyMemberAttribute定义成消息主体(Body)成员。后面的XML体现的是Customer对象在SOAP消息中的表现形式。

1: [MessageContract]

2: public class Customer

3: {

4: [MessageHeader(Name = "CustomerNo", Namespace = "http://www.artech.com/")]

5: public Guid ID

6: { get; set; }

7:?

8: [MessageHeader(Name = "CustomerName", Namespace = "http://www.artech.com/")]

9: public string Name

10: { get; set; }

11:?

12: [MessageBodyMember(Namespace = "http://www.artech.com/")]

13: public string Address

14: { get; set; }

15: }

1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

2: <s:Header>

3: <a:Action s:mustUnderstand="1">http://tempuri.org/IOrderManager/ProcessOrder</a:Action>

4: <h:CustomerName xmlns:h="http://www.artech.com/">Foo</h:CustomerName>

5: <h:CustomerNo xmlns:h="http://www.artech.com/">2f62405b-a472-4d1c-8c03-b888f9bd0df9</h:CustomerNo>

6: </s:Header>

7: <s:Body>

8: <Customer xmlns="http://tempuri.org/">

9: <Address xmlns="http://www.artech.com/">#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</Address>

10: </Customer>

11: </s:Body>

12: </s:Envelope>

如果我们将IsWrapped的属性设为false,那么套在Address节点外的Customer节点将会从SOAP消息中去除。

1: [MessageContract(IsWrapped = false)]

2: public class Customer

3: {

4: //省略成员

5: }

1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

2: ......

3: <s:Body>

4: <Address xmlns="http://www.artech.com/">#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</Address>

5: </s:Body>

6: </s:Envelope>

我们同样可以自定义这个主体封套(Wrapper)的命名和命名空间。下面我们就通过将MessageContractAttributeWrapperNameWrapperNamespace属性设为Custhttp://www.artech.com/

1: [MessageContract(IsWrapped = true, WrapperName = "Cust", WrapperNamespace = "http://www.artech.com/")]

2: public class Customer

3: {

4: //省略成员

5: }

1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

2: ......

3: <s:Body>

4: <Cust xmlns="http://www.artech.com/">

5: <Address>#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</Address>

6: </Cust>

7: </s:Body>

8: </s:Envelope>

2MessageHeaderAttribute

MessageHeaderAttributeMessageBodyMemberAttribute分别用于定义消息报头成员和消息主体成员,它们都有一个共同的基类:System.ServiceModel.MessageContractMemberAttributeMessageContractMemberAttribute定义了以下属性成员:HasProtectionLevelProtectionLevelNameNamespace

1: public abstract class MessageContractMemberAttribute : Attribute

2: {

3: public bool HasProtectionLevel { get; }

4: public ProtectionLevel ProtectionLevel { get; set; }

5:?

6: public string Name { get; set; }

7: public string Namespace { get; set; }

8: }

通过在属性或者字段成员上应用MessageHeaderAttribute使之成为一个消息报头成员。MessageHeaderAttribute定义了以下3个属性,如果读者对SOAP规范有一定了解的读者,相信对它们不会陌生。

注:在《WCF技术剖析(卷1)》中的第六章有对SOAP 1.2的基本规范有一个大致的介绍,读者也可以直接访问W3C网站下载官方文档。

1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]

2: public class MessageHeaderAttribute : MessageContractMemberAttribute

3: {

4: public string Actor { get; set; }

5: public bool MustUnderstand { get; set; }

6: public bool Relay { get; set; }

7: }

同样使用上面定义的Customer消息契约,现在我们相应地修改了ID属性上的MessageHeaderAtribute设置:MustUnderstand = true, Relay=true, Actor=http://www.w3.org/ 2003/05/soap-envelope/role/ultimateReceiver。实际上将相应的SOAP报头的目标SOAP节点定义成最终的消息接收者。由于http://www.w3.org/2003/05/soap-envelope/role/ultimateReceiverSOAP 1.2的预定义属性,所以这个消息契约之后在基于SOAP 1.2的消息版本中有效。后面给出的为对应的SOAP消息。

1: [MessageContract(IsWrapped =true, WrapperNamespace="http://www.artech.com/")]public class Customer

2: {

3: //其他成员

4: [MessageHeader(Name="CustomerNo", Namespace = "http://www.artech.com/" ,MustUnderstand = true, Relay=true, Actor="http://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver" )]

5: public Guid ID

6: { get; set; }

7:

8: }

1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

2: <s:Header>

3: ......

4: <h:CustomerNo s:role="http://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver" s:mustUnderstand="1" s:relay="1" xmlns:h="http://www.artech.com/">5330c91a-7fd7-4bf5-ae3e-4ba9bfef3d4d</h:CustomerNo>

5: </s:Header>

6: ......

7: </s:Envelope>

http://www.w3.org/2003/05/soap-envelope/role/ultimateReceiverSOAP1.1中对应的表示为:"http://schemas.xmlsoap.org/soap/actor/ultimateReceiver(具有不同的命名空间)。如果在SOAP 1.1下,ID成员对应的MessageHeaderAttribute应该做如下的改动。从对应的SOAP消息来看,在SOAP 1.2中的role属性变成了actor属性。

1: [MessageContract(IsWrapped =true, WrapperNamespace="http://www.artech.com/")]public class Customer

2: {

3: //其他成员

4: [MessageHeader(Name="CustomerNo", Namespace = "http://www.artech.com/" ,MustUnderstand = true, Relay=true, Actor="http://schemas.xmlsoap.org/soap/actor/ultimateReceiver" )]

5: public Guid ID

6: { get; set; }

7: }

1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">

2: <s:Header>

3: ......

4: <h:CustomerNo s:actor="http://schemas.xmlsoap.org/soap/actor/ultimateReceiver" s:mustUnderstand="1" xmlns:h="http://www.artech.com/">e48a8897-c644-49f8-b5e7-cd16be4c75b7</h:CustomerNo>

5: </s:Header>

6: ......

7: </s:Envelope>

3MessageBodyMemberAttribute

MessageBodyMemberAttribute应用于属性或者字段成员,应用了该特性的属性或者字段的内容将会出现在SOAP的主体部分。MessageBodyMemberAttribute的定义显得尤为简单,仅仅具有一个Order对象,用于控制成员在SOAP消息主体中出现的位置。默认的排序规则是基于字母排序。

可能细心的读者会问,为什么MessageHeaderAttribute中没有这样Order属性呢?原因很简单,MessageHeaderAttribute定义的是单个SOAP报头,SOAP消息报头集合中的每个报头元素是次序无关的。而MessageBodyMemberAttribute则是定义SOAP主体的某个元素,主体成员之间的次序也是契约的一个重要组成部分。所以MessageHeaderAttribute不叫MessageHeaderMemberAttribute

1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]

2: public class MessageBodyMemberAttribute : MessageContractMemberAttribute

3: {

4: public int Order { get; set; }

5: }

二、实例演示:基于消息契约的方法调用是如何格式化成消息的?

WCF体系中,MessageFormatter负责序列化和反序列化任务(在《WCF技术剖析(卷1)》中的第5章对基于MessageFormatter的序列化机制有详细的介绍):ClientMessageFormatterDispatchMessageFormatter分别在客户端和服务端,根据操作的描述(Operation Description),借助于相应的序列化器(Serializer)实现了方法调用与消息之间的转换。接下来,我将通过一个实实在在的案例程序为大家演示如何通过ClientMessageFormatter将输入参数转换为基于当前服务操作的Message。由于本节的主题是消息契约,所以在这里我们将转换对象限定为消息契约。不过,不论是消息参数还是一般的可序列化对象,其转换过程都是一样的。

步骤一:创建消息契约

本案例模拟一个订单处理的WCF应用,我们首先定义如下一个Order类型。Order是一个消息契约,属性OrderIDDate通过MessageHeaderAttribute定义成消息报头,作为主体的Details的类型OrderDetails被定义成集合数据契约。OrderDetails的元素类型是数据契约OrderDetail,代表订单中每笔产品明细。

1: using System;

2: using System.Collections.Generic;

3: using System.Runtime.Serialization;

4: using System.ServiceModel;

5: namespace Artech.TypedMessage

6: {

7: [MessageContract]

8: public class Order

9: {

10: [MessageHeader(Namespace ="http://www.artech.com/")]

11: public Guid OrderID

12: { get; set; }

13:?

14: [MessageHeader(Namespace ="http://www.artech.com/")]

15: public DateTime Date

以上是关于WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化的主要内容,如果未能解决你的问题,请参考以下文章

WCF:使用带有消息契约的流式传输

C# WCF之用接口创建服务契约部署及客户端连接

WCF - WCF基本概念和简单WCF服务端创建

Java经典编程题50道之十八

JAVA框架技术之十八节springboot课件上手教程

JAVA框架技术之十八节springboot课件上手教程