呼和浩特市网站公司,中华建筑网,公司网站怎么做包括什么,公司网络推广在本篇文章中#xff0c;我们将讨论WCF四大契约#xff08;服务契约、数据契约、消息契约和错误契约#xff09;之一的消息契约#xff08;Message Contract#xff09;。服务契约关注于对服务操作的描述#xff0c;数据契约关注于对于数据结构和格式的描述#xff0c;而…在本篇文章中我们将讨论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、WrapperNamespaceIsWrapped表述的含义是是否为定义的主体成员一个或者多个添加一个额外的根节点。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:ahttp://www.w3.org/2005/08/addressing xmlns:shttp://www.w3.org/2003/05/soap-envelope 2: s:Header 3: a:Action s:mustUnderstand1http://tempuri.org/IOrderManager/ProcessOrder/a:Action 4: h:CustomerName xmlns:hhttp://www.artech.com/Foo/h:CustomerName 5: h:CustomerNo xmlns:hhttp://www.artech.com/2f62405b-a472-4d1c-8c03-b888f9bd0df9/h:CustomerNo 6: /s:Header 7: s:Body 8: Customer xmlnshttp://tempuri.org/ 9: Address xmlnshttp://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:ahttp://www.w3.org/2005/08/addressing xmlns:shttp://www.w3.org/2003/05/soap-envelope 2: ...... 3: s:Body 4: Address xmlnshttp://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:ahttp://www.w3.org/2005/08/addressing xmlns:shttp://www.w3.org/2003/05/soap-envelope 2: ...... 3: s:Body 4: Cust xmlnshttp://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 NodeSOAP1.1中对应的属性Attribute为actorSOAP 1.2中就是我们介绍的role属性 MustUnderstand表述ActorSOAP 1.1或者RoleSOAP 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, Relaytrue, Actorhttp://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, WrapperNamespacehttp://www.artech.com/)]public class Customer 2: { 3: //其他成员 4: [MessageHeader(NameCustomerNo, Namespace http://www.artech.com/ ,MustUnderstand true, Relaytrue, Actorhttp://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver )] 5: public Guid ID 6: { get; set; } 7: 8: } 1: s:Envelope xmlns:ahttp://www.w3.org/2005/08/addressing xmlns:shttp://www.w3.org/2003/05/soap-envelope 2: s:Header 3: ...... 4: h:CustomerNo s:rolehttp://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver s:mustUnderstand1 s:relay1 xmlns:hhttp://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, WrapperNamespacehttp://www.artech.com/)]public class Customer 2: { 3: //其他成员 4: [MessageHeader(NameCustomerNo, Namespace http://www.artech.com/ ,MustUnderstand true, Relaytrue, Actorhttp://schemas.xmlsoap.org/soap/actor/ultimateReceiver )] 5: public Guid ID 6: { get; set; } 7: } 1: s:Envelope xmlns:ahttp://www.w3.org/2005/08/addressing xmlns:shttp://schemas.xmlsoap.org/soap/envelope/ 2: s:Header 3: ...... 4: h:CustomerNo s:actorhttp://schemas.xmlsoap.org/soap/actor/ultimateReceiver s:mustUnderstand1 xmlns:hhttp://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: } 二、实例演示基于消息契约的方法调用是如何格式化成消息的 在WCF体系中MessageFormatter负责序列化和反序列化任务在《WCF技术剖析卷1》中的第5章对基于MessageFormatter的序列化机制有详细的介绍ClientMessageFormatter和DispatchMessageFormatter分别在客户端和服务端根据操作的描述Operation Description借助于相应的序列化器Serializer实现了方法调用与消息之间的转换。接下来我将通过一个实实在在的案例程序为大家演示如何通过ClientMessageFormatter将输入参数转换为基于当前服务操作的Message。由于本节的主题是消息契约所以在这里我们将转换对象限定为消息契约。不过不论是消息参数还是一般的可序列化对象其转换过程都是一样的。 步骤一创建消息契约 本案例模拟一个订单处理的WCF应用我们首先定义如下一个Order类型。Order是一个消息契约属性OrderID和Date通过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 16: { get; set; } 17: 18: [MessageBodyMember] 19: public OrderDetails Details 20: { get; set; } 21: 22: public override string ToString() 23: { 24: return string.Format(Oder ID: {0}\nDate: {1}\nDetail Count: {2},this.OrderID,this.Date.ToShortDateString(),this.Details.Count); 25: } 26: } 27: 28: [CollectionDataContract(ItemName Detail,Namespace http://www.artech.com/)] 29: public class OrderDetails : ListOrderDetail 30: { } 31: 32: [DataContract(Namespace http://www.artech.com/)] 33: public class OrderDetail 34: { 35: [DataMember] 36: public Guid ProductID 37: { get; set; } 38: 39: [DataMember] 40: public int Quantity 41: { get; set; } 42: } 43: } 步骤二创建MessageFormatter 本例的目的在于重现WCF如何通过ClientMessageFormatter实现将输入参数序列化成请求消息以及通过DispatchMessageFormatter实现将请求消息反序列化成输入参数。根据使用的序列化器的不同WCF中定义了两种典型的MessageFormatter一种是基于DataContractSerializer的DataContractSerializerOperationFormatter另一种则是基于XmlSerializer的XmlSerializerOperationFormatter。由于DataContractSerializerOperationFormatter是默认的MessageFormatter所以我们这个案例就采用DataContractSerializerOperationFormatter。 我们的任务就是创建这个DataContractSerializerOperationFormatter。由于这是一个定义在System.ServiceModel.Dispatcher命名空间下的内部internal类型所以我们只能通过反射的机制调用构造函数来创建这个对象。DataContractSerializerOperationFormatter定义了唯一的一个构造函数3个输入参数类型分别为OperationDescriptionDataContractFormatAttribute和DataContractSerializerOperationBehavior。 1: internal class DataContractSerializerOperationFormatter : OperationFormatter 2: { 3: //其他成员 4: public DataContractSerializerOperationFormatter(OperationDescription description, DataContractFormatAttribute dataContractFormatAttribute, DataContractSerializerOperationBehavior serializerFactory); 5: } 为此我们定义下面一个辅助方法CreateMessageFormatterTFormatter, TContract。TFormatter代表MessageFormatter的两个接口IClientMessageFormatter和IDispatchMessageFormatterDataContractSerializerOperationFormatter同时实现了这两个接口TContract则是服务契约的类型。参数operationName为当前操作的名称。代码不算复杂主要的流程如下通过服务契约类型创建ContractDescription根据操作名称得到OperationDescription对象。通过反射机制调用DataContractSerializerOperationFormatter的构造函数创建该对象。 1: static TFormatter CreateMessageFormatterTFormatter, TContract(string operationName) 2: { 3: ContractDescription contractDesc ContractDescription.GetContract(typeof(TContract)); 4: var operationDescs contractDesc.Operations.Where(op op.Name operationName); 5: if(operationDescs.Count() 0) 6: { 7: throw new ArgumentException(operationName,Invalid operation name.); 8: } 9: OperationDescription operationDesc operationDescs.ToArray()[0]; 10: string formatterTypeName System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter,System.ServiceModel, Version3.0.0.0, Cultureneutral, PublicKeyTokenb77a5c561934e089; 11: Type formatterType Type.GetType(formatterTypeName); 12: ConstructorInfo constructor formatterType.GetConstructor(new Type[] { typeof(OperationDescription), typeof(DataContractFormatAttribute), typeof(DataContractSerializerOperationBehavior) }); 13: return (TFormatter)constructor.Invoke(new object[] { operationDesc, new DataContractFormatAttribute(), null }); 14: } MessageFormatter已经创建出来了序列化与反序列化的问题就很简单了。为此我定义了以下两个辅助方法SerializeRequestTContract和DeserializeRequestTContract具体实现就是调用创建出来的MessageFormatter的同名方法。 1: static Message SerializeRequestTContract(MessageVersion messageVersion, string operationName, params object[] values) 2: { 3: IClientMessageFormatter formatter CreateMessageFormatterIClientMessageFormatter, TContract(operationName); 4: return formatter.SerializeRequest(messageVersion, values); 5: } 6: 7: static void DeserializeRequestTContract(Message message, string operationName, object[] parameters) 8: { 9: IDispatchMessageFormatter formatter CreateMessageFormatterIDispatchMessageFormatter, TContract(operationName); 10: formatter.DeserializeRequest(message, parameters); 11: } 步骤三通过MessageFormmatter实现消息的格式化 现在我们通过一个简单的例子来演示通过上面创建的MessageFormatter实现对消息的格式化。由于MessageFormatter进行序列化和反序列化依赖于操作的描述消息的结构本来就是由操作决定的为此我们定义了一个服务契约IOrderManager。操作ProcessOrder将消息契约Order作为唯一的参数。 1: using System.ServiceModel; 2: namespace Artech.TypedMessage 3: { 4: [ServiceContract] 5: public interface IOrderManager 6: { 7: [OperationContract] 8: void ProcessOrder(Order order); 9: } 10: } 在下面的代码中先调用SerializeRequestIOrderManager方法将Order对象进行序列化并生成Message对象该过程实际上体现了WCF的客户端框架是如何通过ClientMessageFormatter将操作方法调用连同输入参数转换成请求消息的。随后调用DeserializeRequestIOrderManager方法将Message对象反序列化成Order对象该过程则代表WCF的服务端框架是如何通过DispatchMessageFormatter将请求消息反序列化成输入参数的。 1: OrderDetail detail1 new OrderDetail 2: { 3: ProductID Guid.NewGuid(), 4: Quantity 666 5: }; 6: 7: OrderDetail detail2 new OrderDetail 8: { 9: ProductID Guid.NewGuid(), 10: Quantity 999 11: }; 12: 13: Order order new Order 14: { 15: OrderID Guid.NewGuid(), 16: Date DateTime.Today, 17: Details new OrderDetails { detail1, detail2 } 18: }; 19: //模拟WCF客户端的序列化 20: Message message SerializeRequestIOrderManager(MessageVersion.Default, ProcessOrder, order); 21: MessageBuffer buffer message.CreateBufferedCopy(int.MaxValue); 22: WriteMessage(buffer.CreateMessage(), message.xml); 23: 24: //模拟WCF服务端的反序列化 25: object[] DeserializedOrder new object[]{ null }; 26: DeserializeRequestIOrderManager(buffer.CreateMessage(), ProcessOrder, DeserializedOrder); 27: Console.WriteLine(DeserializedOrder[0]); 下面的XML表示调用SerializeRequestIOrderManager生成的SOAP消息。程序最终的输出结果也表明了反序列化的成功执行。 1: s:Envelope xmlns:ahttp://www.w3.org/2005/08/addressing xmlns:shttp://www.w3.org/2003/05/soap-envelope 2: s:Header 3: a:Action s:mustUnderstand1http://tempuri.org/IOrderManager/ProcessOrder/a:Action 4: h:Date xmlns:hhttp://www.artech.com/2008-12-21T00:00:0008:00/h:Date 5: h:OrderID xmlns:hhttp://www.artech.com/cd94a6f0-7e21-4ace-83f7-2ddf061cfbbe/h:OrderID 6: /s:Header 7: s:Body 8: Order xmlnshttp://tempuri.org/ 9: Details xmlns:d4p1http://www.artech.com/ xmlns:ihttp://www.w3.org/2001/XMLSchema-instance 10: d4p1:Detail 11: d4p1:ProductIDbc2a186d-569a-4146-9b97-3693248104c0/d4p1:ProductID 12: d4p1:Quantity666/d4p1:Quantity 13: /d4p1:Detail 14: d4p1:Detail 15: d4p1:ProductID72687c23-c2b2-4451-b6c3-da6d040587fc/d4p1:ProductID 16: d4p1:Quantity999/d4p1:Quantity 17: /d4p1:Detail 18: /Details 19: /Order 20: /s:Body 21: /s:Envelope 1: Oder ID: cd94a6f0-7e21-4ace-83f7-2ddf061cfbbe 2: Date: 12/21/2008 3: Detail Count: 2 转载于:https://www.cnblogs.com/qq260250932/p/5350905.html