在本篇文章中,我们将讨论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.MessageContractAttribute、System.ServiceModel.MessageHeaderAttribute、System.ServiceModel.MessageBodyMemberAttribute。MessageContractAttribute应用于类型上,MessageHeaderAttribute和MessageBodyMemberAttribute则应用于属性或者字段成员上,表明相应的数据成员是一个基于SOAP报头的成员还是SOAP主体的成员。先来简单介绍一下这3个特性:
1、MessageContractAttribute
通过在一个类或者结构(Struct)上应用MessageContractAttribute使之成为一个消息契约。从MessageContractAttribute的定义来看,MessageContractAttribute大体上具有以下两种类型的属性成员:
- ProtectionLevel和HasProtectionLevel:表示保护级别,在服务契约中已经对保护级别作了简单的介绍,WCF中通过System.Net.Security.ProtectionLevel枚举定义消息的保护级别。一般有3种可选的保护级别:None、Sign和EncryptAndSign
- IsWrapped、WrapperName、WrapperNamespace:IsWrapped表述的含义是是否为定义的主体成员(一个或者多个)添加一个额外的根节点。WrapperName和WrapperNamespace则表述该根节点的名称和命名空间。IsWrapped、WrapperName、WrapperNamespace的默认是分别为true、类型名称和http://tempuri.org/。
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使之成为一个消息契约。ID和Name属性通过应用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)的命名和命名空间。下面我们就通过将MessageContractAttribute的WrapperName和WrapperNamespace属性设为Cust和http://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>
2、MessageHeaderAttribute
MessageHeaderAttribute和MessageBodyMemberAttribute分别用于定义消息报头成员和消息主体成员,它们都有一个共同的基类:System.ServiceModel.MessageContractMemberAttribute。MessageContractMemberAttribute定义了以下属性成员:HasProtectionLevel、ProtectionLevel、Name和Namespace。
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网站下载官方文档。
- Actor:表示处理该报头的目标节点(SOAP Node),SOAP1.1中对应的属性(Attribute)为actor,SOAP 1.2中就是我们介绍的role属性
- MustUnderstand:表述Actor(SOAP 1.1)或者Role(SOAP 1.2)定义的SOAP节点是否必须理解并处理该节点。对应的SOAP报头属性为mustUnderstand
- Relay:对应的SOAP报头属性为relay,表明该报头是否需要传递到下一个SOAP节点
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/ultimateReceiver是SOAP 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/ultimateReceiver在SOAP1.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>
3、MessageBodyMemberAttribute
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: }
本文出自 “蒋金楠的博客” 博客,请务必保留此出处http://jinnan.blog.51cto.com/1789230/346593