SOAP
SOAP是Simple Object Access Protocol(简单对象访问协议)的简称 而如今SOAP已经成为了符合W3C制定的SOAP规范的消息 允许您使用 XML 在通过低层 Internet 协议连接的系统之间进行通信 它为通过网络消息传输的 XML 信息提供了标准的封套 目前为止W3C一共制定了SOAP1.1和SOAP1.2两个版本的消息规范 对应的命名空间如下
SOAP1.1:http://schemas.xmlsoap.org/soap/envelope
SOAP1.2:http://www.w3.org/2003/05/soap-envelope
下面的XML体现了一个典型的SOAP消息的结构 整个消息被封装在称为SOAP封套的<Envelope>元素中 该元素包含了<Body>表示消息主体 <Header>表示消息报头 可以有多个消息报头
1 <env:Envelope xmlns:env="http://www.w3.org/2003/0S/soap-envelope"> 2 <env:Header> 3 <n:alertcontrol xmlns:n="http://example.org/alertcontrol"> 4 <n:priority>l</n:priority> 5 <n:expires>2001-06-22T14:00:00-0S:00</n:expires> 6 </n:alertconcrol> 7 </env:Header> 8 <env:Body> 9 <m:alert xmlns:m="http://example.org/alert"> 10 <m:msg>Pick up Mary at school at 2pm</m:msg> 11 </m:alert> 12 </env:Body> 13 </env:Envelope>
WS-Addressing
SOAP规范了XML消息的结构 而WS-Addressing则用于规范消息交换中的寻址机制 目前W3C发布了WS-Addressing的两个版本 WS-Addressing2004和WS-Addressing1.0 对应的命名空间如下
WS-Addressing2004:http://schemas.xmlsoap.org/2004/08/addressing
WS-Addressing1.0:http://www.w3.org/2005/08/addressing
下面的XML体现了寻址机制在SOAP消息结构中的应用 标红的几个元素是WS-Addressing规范的消息寻址机制中典型的消息寻址报头元素 它们都放在Header报头中 一个报头可以有多个报头元素
1 <S:Envelope xmlns:S="http://www.w3.org/2003/0S/soap-envelope" xmlns:wsa="http://www.w3.org/200S/0S/addressing"> 2 <S:Header> 3 <wsa:MessageID> http://~xample.com/6B29FC40-CA47-1067-B31D-OODDOI0662DA </wsa:MessageID> 4 <wsa:ReplyTo> 5 <wsa:Address> http://example.com/business/clientl </wsa:Address> . 8 </wsa:ReplyTo> 9 <wsa:To> http://example.com/fabrikam/Purchasing </wsa:To> 10 <wsa:Action> http://example.com/fabrikam/SubmitPO </wsa:Accion> 12 </S:Header> 13 <S:Body> ... </S:Body> 14 </S:Envelope>
WS-Addressing中规范的报头元素有如下几种
可选的报头元素
<To>:以URI的形式表示消息发送到的目标终结点 默认值:http://www.w3.org/2005/08/addressing/anonymous
<Form>:表示发送消息的终结点地址 不常用 因为消息发送的终结点地址不需要手动指定
<ReplyTo>:表示接收回复消息的终结点地址 默认值与<To>元素值等同
<FaultTo>:表示接收回复的错误消息的终结点地址
<MessageID>:以URI的形式表示消息的唯一标识
<RelatesTo>:与某个具有唯一标识的消息关联起来 要关联 只需设<RelatesTo>的值=<MessageID>的值
必须的报头元素
<Action>:以URI的形式来表示消息的意图 比如以URI的形式表示要调用的服务操作 如:http://www.cnblogs.com/Test
消息版本
WCF中用System.ServiceModel.Channels.MessageVersion类表示消息版本 它作为表示消息的System.ServiceModel.Channels.Message的Version属性提供消息版本 消息的版本是由消息使用的SOAP的版本和WS-Addressing的版本共同决定的
MessageVersion具有如下公开属性
Envelope
返回一个EnvelopeVersion对象 表示消息使用的SOAP的版本 Envelope通过以下属性表示SOAP版本
Envelope.None:非SOAP消息
Envelope.Soap11:SOAP1.1版本
Envelope.Soap12:SOAP1.2版本
Addressing
返回一个AddressingVersion对象 表示消息使用的WS-Addressing的版本 Addressing通过以下属性表示WS-Addressing的版本
Addressing.None:消息不支持WS-Addressing
Addressing.WSAddressingAugust2004:WS-Addressing2004版本
Addressing.WSAddressing10:WS-Addressing1.0版本
MessageVersion具有如下静态属性
Soap11
获取使用 SOAP 1.1 的消息版本
Soap12
获取使用 SOAP 1.2 的消息版本
Default
获取WCF所用的默认消息版本 默认消息版本为SOAP1.2和WS-Addressing1.0组合的消息版本
None
获取不使用 SOAP 和 WS-Addressing 的消息版本
Soap11WSAddressingAugust2004
获取SOAP1.1和WS-Addressing2004组合的消息版本
Soap11WSAddressing10
获取SOAP1.1和WS-Addressing1.0组合的消息版本
Soap12WSAddressingAugust2004
获取SOAP1.2和WS-Addressing2004组合的消息版本
Soap12WSAddressing10
获取SOAP1.2和WS-Addressing1.0组合的消息版本
MessageVersion具有如下静态方法
CreateVersion(EnvelopeVersion envelopeVersion)
创建消息的版本 参数指定SOAP版本 默认组合WS-Addressing1.0版本
CreateVersion(EnvelopeVersion envelopeVersion, AddressingVersion addressingVersion)
创建消息的版本 参数指定SOAP版本和WS-Addressing版本
创建消息
Message类的静态方法CreateMessage用于创建消息 有如下重载 (? 测过需要使用管理员身份运行 不知道为什么 )创建后调用实例方法WriteMessage将消息序列化为xml WriteMessage会自动调用DataContractSerializer来序列化消息 需要添加对System.Runtime.Serialization的引用
Message.CreateMessage(MessageVersion version , string action)
例子1
1 using System.ServiceModel.Channels; 2 using System.Diagnostics; 3 4 string action = "http://www.artech.com/ICaculator/Add"; 5 Message message = Message.CreateMessage(MessageVersion.Default, action); 6 XmlWriter writer = new XmlTextWriter("Test.xml", Encoding.UTF8); 7 message.WriteMessage(writer); 8 writer.Close(); 9 message.Close(); 10 Process.Start("Test.xml");
Message.CreateMessage(MessageVersion version , string action , object body)
参数body是一个可被序列化的对象 如
例子2
1 [DataContract(Namespace = "http://www.cnblogs.com/")] 2 public class Order 3 { 4 [DataMember(Order = 1)] 5 public Guid ID { get; set; } 6 [DataMember(Order = 2)] 7 public DateTime Date { get; set; } 8 [DataMember(Order = 3)] 9 public string Customer { get; set; } 10 [DataMember(Order = 4)] 11 public string Address { get; set; } 12 } 13 14 Message message = Message.CreateMessage(MessageVersion.None, action, order); 15 ……
Message.CreateMessage(MessageVersion version , string action , BodyWriter bodywriter)
参数bodywriter是一个派生于抽象类BodyWriter的类型 可重写它的OnWiteBodyContents创建消息 我们使用前一个例子的Test.xml文件 在自定义的myBodyWriter中将其数据读取出来用以创建主体 接着使用Message的WriteMessage方法将创建的消息写入Test2.xml
例子3
1 public class myBodyWriter : BodyWriter 2 { 3 private string fileName { get; set; } 4 public myBodyWriter(string fileName):base(false) 5 { 6 this.fileName = fileName; 7 } 8 protected override void OnWriteBodyContents(XmlDictionaryWriter writer) 9 { 10 XmlReader reader = new XmlTextReader(fileName); 11 while (!reader.EOF) 12 { 13 writer.WriteNode(reader, false); 14 } 15 } 16 } 17 18 string action = "http://www.artech.com/ICaculator/Add"; 19 Message message = Message.CreateMessage(MessageVersion.Default, action, new myBodyWriter("Test.xml")); 20 XmlWriter writer = new XmlTextWriter("Test2.xml", Encoding.UTF8); 21 message.WriteMessage(writer); 22 writer.Close(); 23 message.Close(); 24 Process.Start("Test2.xml");
Message.CreateMessage(MessageVersion version , string action , XmlReader xmlreader)
参数xmlreader用于读取一个xml文件 将其作为CreateMessage的参数 可以将读到的xml序列化为指定版本的SOAP消息 还是使用例子2的Test.xml文件来描述此重载的用法 如
例子4
1 string action = "http://www.artech.com/ICaculator/Add"; 2 Message message = Message.CreateMessage(MessageVersion.Soap11WSAddressing10, action, new XmlTextReader("Test.xml")); 3 XmlWriter writer = new XmlTextWriter("Test3.xml", Encoding.UTF8); 4 message.WriteMessage(writer); 5 writer.Close(); 6 message.Close(); 7 Process.Start("Test3.xml");
Message.CreateMessage(MessageVersion version , MessageFault messagefault , string action)
对于服务来说 请求被正常的处理 则返回一个普通的SOAP消息 错误时则可以使用此重载返回错误消息
1 using System.ServiceModel; 2 using System.ServiceModel.Channels; 3 using System.Diagnostics; 4 5 //自定义错误码 6 FaultCode faultcode=FaultCode.CreateSenderFaultCode("calculatorError","http://www.cnblogs.com"); 7 //错误列表 8 List<FaultReasonText> list=new List<FaultReasonText>{ 9 new FaultReasonText("试图除以0") 10 }; 11 //将错误列表插入错误正文 12 FaultReason reason=new FaultReason(list); 13 //创建错误消息 14 MessageFault msgfault=MessageFault.CreateFault(faultcode,reason); 15 //创建消息 16 string action = "http://www.artech.com/ICaculator/Add"; 17 Message message = Message.CreateMessage(MessageVersion.Soap11WSAddressing10, msgfault, action); 18 XmlWriter writer = new XmlTextWriter("error.xml", Encoding.UTF8); 19 message.WriteMessage(writer); 20 writer.Close(); 21 message.Close(); 22 Process.Start("error.xml");
读取消息
Message类的实例方法GetBody用于读取刚创建的还未被写入文件的消息的主体内容并将内容反序列化为对象 如果消息已被写入文件则无法读取 有如下方法
Message.GetBody<T>()
读取消息主体内容并反序列化为对象返回
1 Order order = new Order 2 { 3 ID = Guid.NewGuid(), 4 Date = DateTime.Now, 5 Address = "xx", 6 Customer = "sam" 7 }; 8 9 string action = "http://www.artech.com/ICaculator/Add"; 10 Message message = Message.CreateMessage(MessageVersion.None, action,order); 11 Order ReadOrder = message.GetBody<Order>(); 12 message.Close(); 13 Console.WriteLine(ReadOrder.Customer + "," + ReadOrder.Address); 14 Console.Read();
Message.GetBody<T>(XmlObjectSerializer serializer)
功能同上 可以指定反序列化的序列化器 默认是DataContractSerializer
Message.GetReaderAtBodyContents()
此方法会返回一个XmlDictionaryReader对象 可以通过此对象进一步提取消息主体部分的内容
写入消息
Message类提供了一系列的WriteXx实例方法用于将消息写入 方法如下
WriteStartBody
WriteBody
WriteBodyContents
WriteStartEnvelope
WriteMessage
拷贝消息
当消息被创建后(未写入的时候) 可以使用Message读取或写入消息 一旦消息被读取后 则不能再次读或写 一旦消息被写入后 则不能再次写读或 即被创建的消息只能读一次 不能再次对消息有任何读写操作 写也是一样的 我们可以通过Message的CreateBufferedCopy实例方法创建消息缓存 并使用MessageBuffer的CreateMessage来重建消息对象 如此可以无限制的重复调用MessageBuffer的CreateMessage创建多个副本 用以重复读或者重复写
1 string action = "http://www.artech.com/ICaculator/Add"; 2 Message message = Message.CreateMessage(MessageVersion.None, action,order); 3 //获取消息缓存 4 MessageBuffer messagebuffer = message.CreateBufferedCopy(int.MaxValue); 5 //创建消息副本1 6 Message copymessage= messagebuffer.CreateMessage(); 7 //读 8 Order ReadOrder = copymessage.GetBody<Order>(); 9 Console.WriteLine(ReadOrder.Customer + "," + ReadOrder.Address); 10 //创建消息副本2 11 Message copymessage2 = messagebuffer.CreateMessage(); 12 //第二次读 13 Order ReadOrder2 = copymessage2.GetBody<Order>(); 14 Console.WriteLine(ReadOrder2.Customer + "," + ReadOrder2.Address); 15 message.Close(); 16 messagebuffer.Close();
消息报头
MessageHeaderInfo类
使用MessageHeaderInfo表示消息报头 他是所有消息报头的抽象基类 具有以下属性
Name
报头的名称
NameSpace
和报头的命名空间
Actor
此属性指定了消息报头的目标接受方 对于SOAP的消息来说 消息主体部分一般都是针对消息的最终接收者的 但消息报头则可能是发送给中介者或者最终接收者 Actor属性就是用于指定某个报头的接收方的角色 SOAP规范使用如下URI表示处理报头的角色
http://schemas.xmlsoap.org/soap/actor/next
http://schemas.xmlsoap.org/soap/actor/ultimateReceiver
http://schemas.xmlsoap.org/soap/actor/none
如果你的消息使用的SOAP版本是1.1版本 则编程中使用以上URI来表示角色 如果是SOAP1.2则使用如下URI来表示角色
http://www.w3c.org/2003/05/soap-envelope/role/next
http://www.w3c.org/2003/05/soap-envelope/role/ultimateReceiver
http://www.w3c.org/2003/05/soap-envelope/role/none
next指定报头的接收方可以是中介者也可以是最终接收者
ultimateReceiver指定报头的接收方是最终接收者
none指定报头的接收方可以是任何角色
MustUnderstand
表示报头是否是强制报头
如果设为true 则认为是一个强制报头 那么如果该报头同时指定了报头接收方(Actor) 则接收方必须处理此报头 即如果你需要强制接收方处理此报头 则需要将此属性设为true 并显示指定Actor 否则不需要指定Actor和MustUnderstand
Relay
表示报头是否是一个中继报头 此属性主要针对消息中介 默认false
IsReferenceParameter
表示报头是否是某个终结点引用的参数
MessageHeader类
MessageHeader派生于MessageHeaderInfo 并重写了基类的4个属性 并为每个属性指定了默认值 Actor默认值为null 其余三个(MustUnderstand、Relay、IsReferenceParameter)默认值为false MessageHeader是抽象类 无法实例化 所以它提供了静态方法CreateHeader来创建报头 该方法具有8个重载版本 大体上所接收的参数如下
参数name指定报头名字、ns指定命名空间、value指定可被序列化的对象、mustUnderstand指定是否是强制报头、serializer指定序列化器 默认是DatacontractSerializer、actor指定处理消息报头的节点类型、relay指定是否是中继报头
MessageHeader具有如下几个方法
IsMessageVersionSupported
判断当前报头是否支持指定的消息属性
WriteHeaderContents/WriteStartHeader
实现对消息或消息报头的写入
MessageHeader<T>类
此类也表示消息报头 但他是一个强类型的消息报头且可以实例化 在需要创建自定义报头时可以使用它来创建强类型的对象来作为消息报头 T类型表示作为消息报头值的对象的类型 此类也具有Actor、MustUnderstand和Relay属性 Content属性用于获取或设置表示消息报头值的对象 此类不继承MessageHeader 所以不能直接将其添加到消息报头集合中 可以调用其GetUntypedHeader方法将自身转换为MessageHeader类型 然后再添加到报头集合 转换时需要定义报头的名字和命名空间 如
1 MessageHeader<string> foo = new MessageHeader<string>("xxx"); 2 message.Headers.Add(foo.GetUntypedHeader("xxxxx","xxxxx"));
MessageHeaders类
此类表示消息报头集合 通过表示消息的Message对象的Headers只读属性可以获取 MessageHeaders具有与前面介绍过的WS-Addressing的7个寻址报头元素一样的可读写的属性 它们是:
To
以URI的形式表示消息发送到的目标终结点
Form
设置或获取发送消息的终结点地址 不常用 因为消息发送的终结点地址不需要手动指定
ReplyTo
设置或获取接收回复消息的终结点地址
FaultTo
设置或获取接收回复的错误消息的终结点地址
MessageID
设置或获取消息的唯一ID
RelatesTo
设置或获取与消息相关的消息ID
Action
以URI的形式来表示消息的意图 比如以URI的形式表示要调用的服务操作 如:http://www.cnblogs.com/Test
这7个属性可用于设置或获取报头的信息 每个属性其实就是一个内置的报头 所以无需手动创建MessageHeader或MessageHeaderInfo 除非自定义报头 除开这7个属性外 此类还具有UnderstoodHeaders只读属性(用于获取所有MustUnderstand为true的报头集合 )、MessageVersion(获取消息版本)
MessageHeaders具有如下方法用于获取单个报头或将单个报头添加到报头集合:
Add(MessageHeader header)
将MessageHeader类型的报头添加到报头集合中
GetHeader<T>(string headerName,string headerNamespace)
根据指定的报头名称和报头的命名空间获取一个报头 T表示报头的类型
现在来演示一遍如何为消息添加寻址报头和自定义报头
1 string ns = "http://www.artech.com/crm"; 2 string action = "http://www.artech.com/crm/addCustomer"; 3 EndpointAddress address = new EndpointAddress("http://www.artech.com/crm/client"); 4 Message message = Message.CreateMessage(MessageVersion.Soap11WSAddressingAugust2004, action); 5 //指定6个寻址报头的值 6 message.Headers.To = new Uri("http://www.artech.com/crm/customerService"); 7 message.Headers.From = address; 8 message.Headers.ReplyTo = address; 9 message.Headers.FaultTo = address; 10 Guid id = Guid.NewGuid(); 11 message.Headers.MessageId = new UniqueId(id); 12 message.Headers.RelatesTo = new UniqueId(id); 13 //自定义的消息报头 14 MessageHeader<string> foo = new MessageHeader<string>("ABC"); 15 //参数3为actor 它指定了自定义报头bar的接收方可以是中介者或者最终接收者 参数2为mustUnderstand 值为true 表示此报头必须被接收者处理 16 MessageHeader<string> bar = new MessageHeader<string>("abc", true, "http://schemas.xmlsoap.org/soap/actor/next",false); 17 MessageHeader<string> baz = new MessageHeader<string>("123",false,"http://schemas.xmlsoap.org/soap/actor/next",false); 18 //转换自定义报头 19 message.Headers.Add(foo.GetUntypedHeader("foo",ns)); 20 message.Headers.Add(foo.GetUntypedHeader("bar", ns)); 21 message.Headers.Add(foo.GetUntypedHeader("baz", ns)); 22 XmlWriter writer = new XmlTextWriter("1.xml", Encoding.UTF8); 23 message.WriteMessage(writer); 24 writer.Close(); 25 message.Close(); 26 Process.Start("1.xml");
结果如下
1 <s:Envelope xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> 2 <s:Header> 3 <a:Action s:mustUnderstand="1">http://www.artech.com/crm/addCustomer</a:Action> 4 <a:To s:mustUnderstand="1">http://www.artech.com/crm/customerService</a:To> 5 <a:From> 6 <a:Address>http://www.artech.com/crm/client</a:Address> 7 </a:From> 8 <a:ReplyTo> 9 <a:Address>http://www.artech.com/crm/client</a:Address> 10 </a:ReplyTo> 11 <a:FaultTo> 12 <a:Address>http://www.artech.com/crm/client</a:Address> 13 </a:FaultTo> 14 <a:MessageID>urn:uuid:c342e0c8-d056-4ba5-8f35-f8a399dd3fd9</a:MessageID> 15 <a:RelatesTo>urn:uuid:c342e0c8-d056-4ba5-8f35-f8a399dd3fd9</a:RelatesTo> 16 <foo xmlns="http://www.artech.com/crm">ABC</foo> 17 <bar xmlns="http://www.artech.com/crm">ABC</bar> 18 <baz xmlns="http://www.artech.com/crm">ABC</baz> 19 </s:Header> 20 <s:Body /> 21 </s:Envelope>
消息属性
Properties
Properties是一个MessageProperties类型的对象 表示消息的消息属性集合 这些消息属性集合是附加到消息中的命名数据段 且通常不会在发送消息时发出 它仅仅在本地使用 如果需要 比如可以将消息属性附加到消息上 以供信道使用 它是一个键为string值为object的MessageProperties字典对象 实现了IDictionary<string,object>接口 它具有如下方法用于获取单个消息属性和添加单个消息属性到消息属性集合:
Add(string name,object property)
根据指定的消息属性名称和消息属性对象添加到消息集合中
MessageProperties[string propertyName]
获取单个消息属性只需要通过为数组指定某个消息属性的名称即可获取单个消息属性对象
Http消息属性
Http消息属性仅用于Http传输方式的消息 可以通过以下两个Http消息属性对象定制Http报头和Http查询字符串、cookie等 以下两个对象都有自己的报头属性集合 这里指的是Http报头 区别于消息的报头 消息的报头是封装在<Envelope>元素里的子元素<Header> 而Http报头不属于<Envelope>元素的子元素 它们是显示在消息的最开头位置的Http消息中的报头
HttpRequestMessageProperty
ns:System.ServiceModel.Channels.HttpRequestMessageProperty
如果传输方式为Http 则可以使用此对象 它也表示消息的属性 但仅用于Http传输方式的SOAP消息 可以使用此对象为SOAP消息添加Http报头和查询字符串 该对象具有如下属性
Name
静态属性 HttpRequestMessageProperty对象在消息的消息属性集合中的键名称 返回httpRequest
Headers
Http请求消息的报头集合
QueryString
查询字符串 默认返回空
SuppressEntityBody
是否忽略Http请求消息的主体 只发送报头部分
HttpResponseMessageProperty
ns:System.ServiceModel.Channels.HttpResponseMessageProperty
与HttpRequestMessageProperty有类似的行为 只不过此对象表示的是Http回复消息
Name
静态属性 HttpResponseMessageProperty对象在消息的消息属性集合中的键名称 返回httpResponse
Headers
Http回复消息的报头集合
SuppressEntityBody
是否忽略Http回复消息的主体 只发送报头部分
StatusCode
以System.Net.HttpStatisCode枚举的形式返回回复状态码
StatusDescriptiom
获取对状态的描述
SuppressPreamble
是否忽略消息序文
服务操作上下文
OperationContext类
服务操作上下文通过OperationContext表示 上下文对象可以获取当前执行的信道、当前线程的执行上下文、消息的消息属性集合和报头集合等等 它通过Current静态属性返回一个OperationContext实例对象 实例属性如下
IncomingMessageHeaders
获取入栈消息的报头集合 返回一个MessageHeaders对象 通过MessageHeaders的Add和GetHeader<T>可添加、获取单个报头
IncomingMessageProperties
获取入栈消息的消息属性集合 返回一个MessageProperties字典对象 通过MessageProperties的Add和[]可添加、获取单个消息属性
OutgoingMessageHeaders
获取出栈消息的报头集合 返回一个MessageHeaders对象
OutgoingMessageProperties
获取出栈消息的消息属性集合 返回一个MessageProperties字典对象
实例1:使用第一章的Calculator计算服务的例子 通过服务操作上下文对象为请求和回复消息添加http报头cookie
首先将Client项目的app.config的消息发送地址的端口改为8888
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <client> 5 <endpoint address="http://127.0.0.1:8888/calculatorservice" binding="basicHttpBinding" contract="CalculatorService" name="calculatorservice" /> 6 </client> 7 </system.serviceModel> 8 </configuration>
再将Hosting项目的app.config的消息侦听地址改为7777
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <services> 5 <service name="Service.CalculatorService"> 6 <endpoint address="http://127.0.0.1:7777/calculatorservice" binding="basicHttpBinding" contract="CalculatorService"/> 7 </service> 8 </services> 9 </system.serviceModel> 10 </configuration>
在Service服务操作中为出栈消息添加Http报头
1 using System.ServiceModel; 2 using Service.Interface; 3 using System.ServiceModel.Channels; 4 using System.Net; 5 using System.Diagnostics; 6 7 8 namespace Service 9 { 10 [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)] 11 public class CalculatorService:ICalculator 12 { 13 public double Add(double x, double y) 14 { 15 16 string responseMsgName = HttpResponseMessageProperty.Name; 17 //创建Http消息的消息属性并添加到消息属性集合中 18 HttpResponseMessageProperty messageProperty = new HttpResponseMessageProperty(); 19 OperationContext.Current.OutgoingMessageProperties.Add(responseMsgName, messageProperty); 20 //为消息属性添加报头 21 messageProperty.Headers.Add(HttpResponseHeader.SetCookie, "Timestamp=" + Stopwatch.GetTimestamp()); 22 return x + y; 23 } 24 } 25 }
在Client项目的请求消息中添加出栈消息的Http报头
1 using System.ServiceModel; 2 using System.ServiceModel.Channels; 3 using Service.Interface; 4 using System.Net; 5 6 namespace Client 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 string securityToke = Guid.NewGuid().ToString(); 13 string requestMsgName = HttpRequestMessageProperty.Name; 14 ChannelFactory<ICalculator> ChannelFactory = new ChannelFactory<ICalculator>("calculatorservice"); 15 ICalculator proxy = ChannelFactory.CreateChannel(); 16 OperationContextScope scope = new OperationContextScope(proxy as IContextChannel); 17 HttpRequestMessageProperty requestMsgPerty = new HttpRequestMessageProperty(); 18 OperationContext.Current.OutgoingMessageProperties.Add(requestMsgName, requestMsgPerty); 19 requestMsgPerty.Headers.Add(HttpRequestHeader.Cookie, "SecurityToken=" + securityToke); 20 //调用服务 21 proxy.Add(1, 2); 22 //释放资源 23 ChannelFactory.Close(); 24 scope.Dispose(); 25 Console.Read(); 26 } 27 } 28 }
使用tcpTrace拦截消息 输出如下
注意配置终结点时 binding必须是basicHttpBinding 因为HttpResponseMessageProperty和HttpRequestMessageProperty仅仅适用于Http方式的请求和回复
实例2:使用第一章的Calculator计算服务的例子 客户端将回复消息的报头读取出来显示 服务端将请求消息的报头读取出来显示
要实现此操作 可以在契约接口中定义一个字典集合数据契约类 将字典集合数据契约类作为自定义报头的类型
1 using System.Runtime.Remoting.Messaging; 2 using System.Runtime.Serialization; 3 4 namespace Service.Interface 5 { 6 //指定序列化后 集合数据契约的元素名(ItemName)、键的元素名(KeyName)、值的元素名(ValueName) 7 [CollectionDataContract(Namespace = "http://www.cnblogs.com/", ItemName = "Message", KeyName = "Key", ValueName = "Value")] 8 public class ApplicationContext : Dictionary<string, string> 9 { 10 public const string callContextKey = "__applicationContext"; 11 public static string HeaderLocalName = "ApplicationContext"; 12 public static string HeaderNamespace = "http://www.cnblogs.com/"; 13 public static ApplicationContext Current 14 { 15 get 16 { 17 if (CallContext.GetData(callContextKey) == null) 18 { 19 CallContext.SetData(callContextKey, new ApplicationContext()); 20 } 21 return (ApplicationContext)CallContext.GetData(callContextKey); 22 } 23 } 24 25 //参与序列化的成员 26 public string UserName 27 { 28 get { return (this.ContainsKey("UserName") == true ? this["UserName"] : string.Empty); } 29 set { this["UserName"] = value; } 30 } 31 32 //参与序列化的成员 33 public string Department 34 { 35 get { return (this.ContainsKey("Department") == true ? this["Department"] : string.Empty); } 36 set { this["Department"] = value; } 37 } 38 } 39 }
Client项目发送请求消息 并附加自定义报头
1 using System.ServiceModel; 2 using Service.Interface; 3 4 using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice")) 5 { 6 var proxy = channelFactory.CreateChannel(); 7 using (OperationContextScope scope = new OperationContextScope(proxy as IContextChannel)) 8 { 9 //创建出栈消息的自定义报头 10 ApplicationContext.Current.UserName = "Sam"; 11 ApplicationContext.Current.Department = "IT"; 12 //将自定义的字典集合数据契约作为自定义报头的类型 13 MessageHeader<ApplicationContext> OutHeader = new MessageHeader<ApplicationContext>(ApplicationContext.Current); 14 //将自定义报头添加到报头集合 15 OperationContext.Current.OutgoingMessageHeaders.Add(OutHeader.GetUntypedHeader(ApplicationContext.HeaderLocalName, ApplicationContext.HeaderNamespace)); 16 Console.WriteLine("出栈消息的自定义报头值:{0}", ApplicationContext.Current.UserName); 17 Console.WriteLine("出栈消息的自定义报头值:{0}", ApplicationContext.Current.Department); 18 Console.WriteLine(proxy.Add(1,2)); 19 //获取入栈消息的自定义报头 20 ApplicationContext InHeader = OperationContext.Current.IncomingMessageHeaders.GetHeader<ApplicationContext>(ApplicationContext.HeaderLocalName, ApplicationContext.HeaderNamespace); 21 Console.WriteLine("入栈消息的自定义报头值:{0}", InHeader.UserName); 22 Console.WriteLine("入栈消息的自定义报头值:{0}", InHeader.Department); 23 Console.Read(); 24 } 25 }
Service项目回复消息 并附加自定义报头
1 using System.ServiceModel; 2 using Service.Interface; 3 4 [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)] 5 public class CalculatorService:ICalculator 6 { 7 public double Add(double x, double y) 8 { 9 //获取入栈消息的报头集合 取出ApplicationContext报头 10 ApplicationContext InHeader = OperationContext.Current.IncomingMessageHeaders.GetHeader<ApplicationContext>(ApplicationContext.HeaderLocalName, ApplicationContext.HeaderNamespace); 11 Console.WriteLine("入栈消息的自定义报头值:{0}", InHeader.UserName); 12 Console.WriteLine("入栈消息的自定义报头值:{0}", InHeader.Department); 13 //将服务端的ApplicationContext添加到回复消息的报头集合中 14 ApplicationContext.Current.UserName = "Leo"; 15 ApplicationContext.Current.Department = "Admin"; 16 MessageHeader<ApplicationContext> OutHeader = new MessageHeader<ApplicationContext>(ApplicationContext.Current); 17 OperationContext.Current.OutgoingMessageHeaders.Add(OutHeader.GetUntypedHeader(ApplicationContext.HeaderLocalName, ApplicationContext.HeaderNamespace)); 18 Console.WriteLine("出栈消息的自定义报头值:{0}", ApplicationContext.Current.UserName); 19 Console.WriteLine("出栈消息的自定义报头值:{0}", ApplicationContext.Current.Department); 20 return x + y; 21 } 22 }
客户端和服务端分别输出如下消息
*创建消息报头使用MessageHeader<T> 创建完成后 将其转换为MessageHeader 然后使用OperationContext.Current.OutgoingMessageHeaders的Add方法将报头添加到报头集合中
*创建Http报头 则使用HttpRequestMessageProperty或HttpResponseMessageProperty的Headers.Add方法 添加完成后 使用OperationContext.Current.OutgoingMessageProperties的Add方法将HttpRequestMessageProperty或HttpResponseMessageProperty添加到消息属性集合中
消息契约
MessageContract特性
作为WCF四大契约(服务契约、数据契约、消息契约、错误契约)之一的消息契约 可以将接口或类声明为消息契约 可以指定接口或类的成员为消息报头或消息主体 使用MessageContract特性表示消息契约 它有如下属性
ProtectionLevel
设置消息契约的安全保护级别
IsWrapped
是否将表示消息主体的成员封装到一个父元素下 默认值true
WrapperName
指定要封装表示消息主体成员的父元素的名字 默认值是消息契约类型的名字
WrapperNameSpace
指定要封装表示消息主体成员的父元素的命名空间 默认值Http://tempuri.org
MessageHeader特性
使用该特性表明消息契约的消息成员将作为消息的消息报头 它有如下属性
ProtectionLevel
设置消息契约成员的安全保护级别
Name
消息契约成员的别名 此别名会体现在生成的xml消息中 如果没有别名 则默认为成员的名字
NameSpace
消息契约成员的命名空间
Actor
指定此报头的目标接受方 默认空
MustUnderstand
指定此报头是否是强制报头 默认false
Relay
指定此报头是否是中继报头 默认false
MessageBodyMember特性
使用该特性表明消息契约的消息成员将作为消息的消息主体 它只具有一个属性Order 用于指定此成员出现在消息主体中的位列
消息转换器
TypedMessageConverter类
ns:System.ServiceModel.Description
此类提供了方法用于消息契约和消息之间的转换 它具有如下几个方法
静态方法 Create(Type messageContractType , string action)
TypedMessageConverter类是一个抽象类 只能通过调用自身的静态方法Create来创建TypedMessageConverter对象 参数messageContractType 表示消息契约的类型 参数action表示要调用的服务操作的URI表示
FromMessage(Message message)
将消息转换为消息契约
ToMessage(object messageContractObject)
将参数指定的消息契约的实例对象转换为消息 返回一个Message对象 如
1 using System.ServiceModel; 2 using System.ServiceModel.Description; 3 4 [MessageContract] 5 public class Employee 6 { 7 [MessageHeader] 8 public string ID { get; set; } 9 [MessageBodyMember] 10 public string Name { get; set; } 11 [MessageBodyMember] 12 public string Gender { get; set; } 13 [MessageBodyMember] 14 public string Department { get; set; } 15 } 16 17 Employee employee = new Employee 18 { 19 ID = "xxx0101", 20 Gender = "男", 21 Name = "sam", 22 Department = "IT" 23 }; 24 25 string action = "http://www.cnblogs.com/employee/AddEmployee"; 26 TypedMessageConverter typedMessageConverter=TypedMessageConverter.Create(typeof(Employee), action); 27 Message message=typedMessageConverter.ToMessage(employee); 28 XmlWriter writer = new XmlTextWriter("1.xml", Encoding.UTF8); 29 message.WriteMessage(writer); 30 writer.Close(); 31 message.Close(); 32 Process.Start("1.xml");
生成的xml结构如下 可以看到消息版本采用WCF默认的SOAP1.2和WS-Addressing1.0 ID成员作为报头 其它几个成员则作为了消息的主体
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://www.cnblogs.com/employee/AddEmployee</a:Action> 4 <h:ID xmlns:h="http://tempuri.org/">xxx0101</h:ID> 5 </s:Header> 6 <s:Body> 7 <Employee xmlns="http://tempuri.org/"> 8 <Department>IT</Department> 9 <Gender>男</Gender> 10 <Name>sam</Name> 11 </Employee> 12 </s:Body> 13 </s:Envelope>
*数据契约一般用于服务操作的输入和输出参数 消息契约同样可以作为操作的输入参数 但有限制 如果将消息契约作为服务操作的参数 则该操作只能有这一个参数 不能有额外参数 且 不能讲消息契约作为操作的输出参数
XML编码
消息在通过传输层发送之前必须经过编码 而通过传输层接收到的消息则需要解码才能被处理 编码使用XmlDictionaryWriter 解码使用XmlDictionaryReader
XmlDictionary类
ns:System.Xml
消息的编码对象和消息的解码对象必须对它们所处理的消息具有相同的理解才能正确编码解码消息 而它们对于消息的理解是建立在一个词汇表上的 即编码方按照词汇表对消息进行编码 解码方则按照词汇表对消息进行解码 词汇表使用XmlDictionary类来表示
XmlDictionaryString类
表示Xml键值的对象 而XmlDictionary就是一组XmlDictionaryString对象的集合 但XmlDictionaryString并不实现IEnumerable接口 不能对其进行遍历 可以直接创建XmlDictionaryString对象将其添加到XmlDictionary中 如
1 XmlDictionary dictionary = new XmlDictionary(); 2 XmlDictionaryString obj1 = new XmlDictionaryString(dictionary, "id", 1); 3 XmlDictionaryString obj2 = new XmlDictionaryString(dictionary, "employee", 2); 4 XmlDictionaryString obj3 = new XmlDictionaryString(dictionary, "address", 3);
或者通过XmlDictionary的Add方法 添加XmlDictionaryString对象
XmlDictionary dictionary = new XmlDictionary(); dictionary.Add("id"), dictionary.Add("employee"), dictionary.Add("address")
使用第二种方式 则添加的XmlDictionaryString对象的Key会按添加顺序依次为1-N 假设我们需要将员工信息编码 则可以将id、employee、address存进XmlDictionaryString中 由于每个Xml元素对应着XmlDictionaryString中的Value 所以编码时可以将这些名称替换为XmlDictionaryString的Key
XmlDictionaryWriter
此类实现对Xml的编码 它是一个抽象类 提供一系列静态的CreateXxx方法来创建编码对象
XmlDictionaryWriter.CreateTextWriter()
此方法基于纯文本进行编码 具有多个重载版 这里列出其所有的参数进行说明 参数如下
stream:Stream类型的、表示数据流的对象 编码后数据将写入此流
encoding:支持UTF-8和Unicode两种编码方式 默认值是UTF-8
ownsStream:布尔值 表明创建的编码对象是否拥有对应的Stream对象 如果是 则关闭编码对象对象时 相应的流也会自动关闭 默认值为true
1 Employee employee = new Employee 2 { 3 ID = "001", 4 Gender = "男", 5 Name = "sam", 6 Department = "IT" 7 }; 8 9 using (MemoryStream stream = new MemoryStream()) 10 { 11 using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(stream, Encoding.UTF8, false)) 12 { 13 DataContractSerializer serializer = new DataContractSerializer(typeof(Employee)); 14 serializer.WriteObject(writer, employee); 15 } 16 17 long count = stream.Position; 18 byte[] bytes = stream.ToArray(); 19 StreamReader reader = new StreamReader(stream); 20 stream.Position = 0; 21 string content = reader.ReadToEnd(); 22 23 Console.WriteLine("字节数:{0}\n", count); 24 Console.WriteLine("编码后的二进制表示:{0}\n", BitConverter.ToString(bytes)); 25 Console.WriteLine("编码后的文本表示:{0}", content); 26 Console.Read(); 27 }
结果
1 字节数:203 2 3 编码后的二进制表示:3C-45-6D-70-6C-6F-79-65-65-20-78-6D-6C-6E-73-3D-22-68-74-74- 4 70-3A-2F-2F-73-63-68-65-6D-61-73-2E-64-61-74-61-63-6F-6E-74-72-61-63-74-2E-6F-72 5 -67-2F-32-30-30-34-2F-30-37-2F-43-6C-69-65-6E-74-22-20-78-6D-6C-6E-73-3A-69-3D-2 6 2-68-74-74-70-3A-2F-2F-77-77-77-2E-77-33-2E-6F-72-67-2F-32-30-30-31-2F-58-4D-4C- 7 53-63-68-65-6D-61-2D-69-6E-73-74-61-6E-63-65-22-3E-3C-44-65-70-61-72-74-6D-65-6E 8 -74-3E-49-54-3C-2F-44-65-70-61-72-74-6D-65-6E-74-3E-3C-47-65-6E-64-65-72-3E-E7-9 9 4-B7-3C-2F-47-65-6E-64-65-72-3E-3C-49-44-3E-30-30-31-3C-2F-49-44-3E-3C-4E-61-6D- 10 65-3E-73-61-6D-3C-2F-4E-61-6D-65-3E-3C-2F-45-6D-70-6C-6F-79-65-65-3E 11 12 编码后的文本表示:<Employee xmlns="http://schemas.datacontract.org/2004/07/Clien 13 t" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Department>IT</Departmen 14 t><Gender>男</Gender><ID>001</ID><Name>sam</Name></Employee>
XmlDictionaryWriter.CreateBinaryWriter()
此方法基于二进制进行编码 具有多个重载版 这里列出其所有的参数进行说明 参数如下
stream:Stream类型的、表示数据流的对象 编码后数据将写入此流
dictionary:一个XmlDictionary类型的对象 前面我们说通过XmlDictionary可以表示编码解码的词汇表 提供一个词汇表 则可以使编码后的字节数更少
session:一个XmlBinaryReaderSession类型的对象 允许以动态的方式管理经过优化的字符
ownsStream:布尔值 表明创建的编码对象是否拥有对应的Stream对象 如果是 则关闭编码对象对象时 相应的流也会自动关闭 默认值为true
dictionary配合session使用 则会发挥编码后字节节省量的威力 通过下面的例子可以说明使用dictionary配合session和不使用这两个参数时编码后的字节量的大小
1 using (MemoryStream stream = new MemoryStream()) 2 { 3 XmlBinaryWriterSession session = new XmlBinaryWriterSession(); 4 XmlDictionary dictionary=new XmlDictionary(); 5 dictionary.Add("ID"); 6 dictionary.Add("Gender"); 7 dictionary.Add("Department"); 8 9 using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream,dictionary,session,false)) 10 { 11 DataContractSerializer serializer = new DataContractSerializer(typeof(Employee)); 12 serializer.WriteObject(writer, employee); 13 } 14 15 long count = stream.Position; 16 byte[] bytes = stream.ToArray(); 17 StreamReader reader = new StreamReader(stream); 18 stream.Position = 0; 19 string content = reader.ReadToEnd(); 20 21 Console.WriteLine("字节数:{0}\n", count); 22 Console.WriteLine("编码后的二进制表示:{0}\n", BitConverter.ToString(bytes)); 23 Console.WriteLine("编码后的文本表示:{0}", content); 24 Console.Read(); 25 }
当使用词汇表和session时 编码字节量最少 结果如下
1 字节数:36 2 编码后的二进制表示:42-01-0A-03-0B-01-69-05-42-07-99-02-49-54-42-09-99-03-E7-94- 3 B7-42-0B-99-03-30-30-31-42-0D-99-03-73-61-6D-01
不使用时 则结果为
1 字节数:153 2 编码后的二进制表示:40-08-45-6D-70-6C-6F-79-65-65-08-2E-68-74-74-70-3A-2F-2F-73- 3 63-68-65-6D-61-73-2E-64-61-74-61-63-6F-6E-74-72-61-63-74-2E-6F-72-67-2F-32-30-30 4 -34-2F-30-37-2F-43-6C-69-65-6E-74-09-01-69-29-68-74-74-70-3A-2F-2F-77-77-77-2E-7 5 7-33-2E-6F-72-67-2F-32-30-30-31-2F-58-4D-4C-53-63-68-65-6D-61-2D-69-6E-73-74-61- 6 6E-63-65-40-0A-44-65-70-61-72-74-6D-65-6E-74-99-02-49-54-40-06-47-65-6E-64-65-72 7 -99-03-E7-94-B7-40-02-49-44-99-03-30-30-31-40-04-4E-61-6D-65-99-03-73-61-6D-01
XmlDictionaryWriter.CreateMtomWriter()
此方法基于MTOM进行编码 当使用此方法对消息进行编码 最终会生成一个具有报头和主体的MIME Multipart/Related XOP数据包 XML内容经过编码被放到主体部分 此方法具有多个重载版 这里列出其所有的参数进行说明 参数如下
stream:Stream类型的、表示数据流的对象 编码后数据将写入此流
encoding:支持UTF-8和Unicode两种编码方式 默认值是UTF-8
ownsStream:布尔值 表明创建的编码对象是否拥有对应的Stream对象 如果是 则关闭编码对象对象时 相应的流也会自动关闭 默认值为true
maxSizeInBytes:
startInfo:表示被编码的xml对应的Content-Type的Type属性
boundary:表示被编码的xml的分隔符
startUri:表示被编码的xml对应的Content-ID
writeMessageHeaders:是否写入MIME Multipart/Related XOP数据包的报头内容
1 using (MemoryStream stream = new MemoryStream()) 2 { 3 string startInfo = "application/soap+xml"; 4 string boundary = "http://www.cnblogs.com/boundary"; 5 string startUri = "http://www.cnblogs.com/contentid"; 6 7 using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateMtomWriter(stream,Encoding.UTF8,int.MaxValue,startInfo,boundary,startUri,true,false)) 8 { 9 DataContractSerializer serializer = new DataContractSerializer(typeof(Employee)); 10 serializer.WriteObject(writer, employee); 11 } 12 13 long count = stream.Position; 14 byte[] bytes = stream.ToArray(); 15 StreamReader reader = new StreamReader(stream); 16 stream.Position = 0; 17 string content = reader.ReadToEnd(); 18 19 Console.WriteLine("字节数:{0}\n", count); 20 Console.WriteLine("编码后的二进制表示:{0}\n", BitConverter.ToString(bytes)); 21 Console.WriteLine("编码后的文本表示:{0}", content); 22 Console.Read(); 23 }
输出结果比较冗余 因为此方法基于MTOM只针对大型二进制数据的编码才会发挥优化数据的能力
1 字节数:638 2 3 编码后的二进制表示:4D-49-4D-45-2D-56-65-72-73-69-6F-6E-3A-20-31-2E-30-0D-0A-43- 4 6F-6E-74-65-6E-74-2D-54-79-70-65-3A-20-6D-75-6C-74-69-70-61-72-74-2F-72-65-6C-61 5 -74-65-64-3B-74-79-70-65-3D-22-61-70-70-6C-69-63-61-74-69-6F-6E-2F-78-6F-70-2B-7 6 8-6D-6C-22-3B-62-6F-75-6E-64-61-72-79-3D-22-68-74-74-70-3A-2F-2F-77-77-77-2E-63- 7 6E-62-6C-6F-67-73-2E-63-6F-6D-2F-62-6F-75-6E-64-61-72-79-22-3B-73-74-61-72-74-3D 8 -22-3C-68-74-74-70-3A-2F-2F-77-77-77-2E-63-6E-62-6C-6F-67-73-2E-63-6F-6D-2F-63-6 9 F-6E-74-65-6E-74-69-64-3E-22-3B-73-74-61-72-74-2D-69-6E-66-6F-3D-22-61-70-70-6C- 10 69-63-61-74-69-6F-6E-2F-73-6F-61-70-2B-78-6D-6C-22-0D-0A-0D-0A-2D-2D-68-74-74-70 11 -3A-2F-2F-77-77-77-2E-63-6E-62-6C-6F-67-73-2E-63-6F-6D-2F-62-6F-75-6E-64-61-72-7 12 9-0D-0A-43-6F-6E-74-65-6E-74-2D-49-44-3A-20-3C-68-74-74-70-3A-2F-2F-77-77-77-2E- 13 63-6E-62-6C-6F-67-73-2E-63-6F-6D-2F-63-6F-6E-74-65-6E-74-69-64-3E-0D-0A-43-6F-6E 14 -74-65-6E-74-2D-54-72-61-6E-73-66-65-72-2D-45-6E-63-6F-64-69-6E-67-3A-20-38-62-6 15 9-74-0D-0A-43-6F-6E-74-65-6E-74-2D-54-79-70-65-3A-20-61-70-70-6C-69-63-61-74-69- 16 6F-6E-2F-78-6F-70-2B-78-6D-6C-3B-63-68-61-72-73-65-74-3D-75-74-66-2D-38-3B-74-79 17 -70-65-3D-22-61-70-70-6C-69-63-61-74-69-6F-6E-2F-73-6F-61-70-2B-78-6D-6C-22-0D-0 18 A-0D-0A-3C-45-6D-70-6C-6F-79-65-65-20-78-6D-6C-6E-73-3D-22-68-74-74-70-3A-2F-2F- 19 73-63-68-65-6D-61-73-2E-64-61-74-61-63-6F-6E-74-72-61-63-74-2E-6F-72-67-2F-32-30 20 -30-34-2F-30-37-2F-43-6C-69-65-6E-74-22-20-78-6D-6C-6E-73-3A-69-3D-22-68-74-74-7 21 0-3A-2F-2F-77-77-77-2E-77-33-2E-6F-72-67-2F-32-30-30-31-2F-58-4D-4C-53-63-68-65- 22 6D-61-2D-69-6E-73-74-61-6E-63-65-22-3E-3C-44-65-70-61-72-74-6D-65-6E-74-3E-49-54 23 -3C-2F-44-65-70-61-72-74-6D-65-6E-74-3E-3C-47-65-6E-64-65-72-3E-E7-94-B7-3C-2F-4 24 7-65-6E-64-65-72-3E-3C-49-44-3E-30-30-31-3C-2F-49-44-3E-3C-4E-61-6D-65-3E-73-61- 25 6D-3C-2F-4E-61-6D-65-3E-3C-2F-45-6D-70-6C-6F-79-65-65-3E-0D-0A-2D-2D-68-74-74-70 26 -3A-2F-2F-77-77-77-2E-63-6E-62-6C-6F-67-73-2E-63-6F-6D-2F-62-6F-75-6E-64-61-72-7 27 9-2D-2D-0D-0A 28 29 编码后的文本表示:MIME-Version: 1.0 30 Content-Type: multipart/related;type="application/xop+xml";boundary="http://www. 31 cnblogs.com/boundary";start="<http://www.cnblogs.com/contentid>";start-info="app 32 lication/soap+xml" 33 34 --http://www.cnblogs.com/boundary 35 Content-ID: <http://www.cnblogs.com/contentid> 36 Content-Transfer-Encoding: 8bit 37 Content-Type: application/xop+xml;charset=utf-8;type="application/soap+xml" 38 39 <Employee xmlns="http://schemas.datacontract.org/2004/07/Client" xmlns:i="http:/ 40 /www.w3.org/2001/XMLSchema-instance"><Department>IT</Department><Gender>男</Gend 41 er><ID>001</ID><Name>sam</Name></Employee> 42 --http://www.cnblogs.com/boundary--
XML解码
XmlDictionaryReader类
此类用于对消息进行解码 还原编码前的XML数据 此类具有如下静态方法
XmlDictionaryReader.CreateTextReader()
对基于纯文本的数据进行解码 具有多个重载版 这里列出其所有的参数进行说明 参数如下
stream:Stream类型的、表示数据流的对象 解码后数据将写入此流
encoding:支持UTF-8和Unicode两种编码方式 默认值是UTF-8
quotas:一个System.Xml.XmlDictionaryReaderQuotas对象 该对象提供5个属性 MaxArrayLength表示允许的最大数组长度 默认值16384 MaxBytesPerRead表示每次读取的最大字节数 默认值4096 MaxDepth表示节点最大嵌套深度 默认值32 MaxStringContentLength表示读取的字符串的最大长度 默认值8192 MaxNameTableCharCount表示表中允许的最大字符数 默认值16384 quotas主要用于限制客户端恶意攻击 传输大体量的数据包让服务端解码 耗费服务端计算资源
buffer:读取数据的缓冲区
XmlDictionaryReader.CreateBinaryReader()
对基于二进制的数据进行解码 具有多个重载版 所有参数同上
XmlDictionaryReader.CreateMtomReader()
对基于基于MTOM进行编码的数据进行解码 具有多个重载版 所有参数同上
编码解码绑定元素
我们说绑定由绑定元素构成 而编码解码的绑定元素则有三种类型 它们是:
BinaryMessageEncodingBindingElement类
此类表示基于二进制的编码绑定元素
TextMessageEncodingBindingElement类
此类表示基于纯文本的编码绑定元素
MtomMessageEncodingBindingElement类
此类表示基于MTOM的编码绑定元素
WCF对消息进行编码解码时 是采用的以上哪种类型的编码绑定元素呢? 这主要是看WCF使用的是何种类型的绑定对象 如果是以Net开头的绑定对象采用的编码解码绑定元素是BinaryMessageEncodingBindingElement类 而BasicHttpBinding和以WS开头的绑定对象采用的编码解码绑定元素则是TextMessageEncodingBindingElement类和MtomMessageEncodingBindingElement类 而WCF采用的又是何种类型的编码解码器呢 则由绑定的编码解码绑定元素来决定 以上三种类型的编码解码绑定元素都派生于抽象基类MessageEncodingBindingElement类 此类提供抽象方法CreateMessageEncoderFactory 此方法用于返回一个用于创建编码解码器的MessageEncoderFactory编码解码器工厂对象 根据不同类型的编码解码绑定元素 编码解码器工厂调用自身的Encoder属性来返回对应的编码解码器对象
编码解码器
MessageEncoder
ns:System.ServiceModel.Channels
WCF最终是通过编码解码器来实现对消息的编码解码的 编码解码器由MessageEncoder类表示 它提供了以下两个方法用于消息的编码与解码
ReadMessage()
解码消息 此方法具有多个重载版 这里列出其所有的参数进行说明 参数如下
stream:从指定的流中读取消息的流对象
maxSizeOfHeaders:从消息中读取的报头的最大大小
buffer:从指定的缓冲区中读取消息的缓冲区对象
bufferManager:管理缓冲区的对象
WriteMessage()
编码消息 此方法具有多个重载版 这里列出其所有的参数进行说明 参数如下
message:消息对象
stream:将消息编码后写入的流对象
maxMessageSize:可写入的最大消息大小
bufferManager:管理缓冲区的对象
编码解码器工厂
MessageEncoderFactory
ns:System.ServiceModel.Channels
编码器是通过编码器工厂创建的 编码器工厂使用MessageEncoderFactory来表示 它提供Encoder属性来获取一个具体的消息编码解码器 而编码解码器工厂是个抽象类 不能直接创建 此工厂类是通过编码解码绑定元素的CreateMessageEncoderFactory来创建的 即具体的编码解码绑定元素通过调用自身的一个方法来创建编码解码器工厂对象 使用编码解码器工厂对象来创建具体的编码解码器对象 如
1 using System.ServiceModel.Channels; 2 3 BinaryMessageEncodingBindingElement encoderBindingElm = new BinaryMessageEncodingBindingElement();//基于二进制的编码解码绑定元素 4 MessageEncoderFactory encoderFactory = encoderBindingElm.CreateMessageEncoderFactory();//创建编码解码器工厂 5 MessageEncoder encoder = encoderFactory.Encoder;//创建编码解码器
配置自定义编码解码绑定元素
对于自定义绑定来说 我们可以自由组合构成绑定的绑定元素 这意味着可以选择我们需要的编码解码绑定元素 一般通过配置的方式来实现 自定义绑定的xml元素是<bindings>的子元素<customBinding> textMessageEncoding配置元素则对应了编码解码的绑定元素TextMessageEncodingBindingElement
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <bindings> 5 <customBinding> 6 <binding> 7 <textMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16"/> 8 </binding> 9 </customBinding> 10 </bindings> 11 </system.serviceModel> 12 </configuration>
binaryMessageEncoding配置元素对应了编码解码的绑定元素BinaryMessageEncodingBindingElement
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <bindings> 5 <customBinding> 6 <binding> 7 <binaryMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16" maxSessionSize="2084"/> 8 </binding> 9 </customBinding> 10 </bindings> 11 </system.serviceModel> 12 </configuration>
mtomMessageEncoding配置元素对应了编码解码的绑定元素MtomMessageEncodingBindingElement
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <bindings> 5 <customBinding> 6 <binding> 7 <mtomMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16"> 8 <readerQuotas maxArrayLength="16384" maxBytesPerRead="4096" maxDepth="32" 9 maxStringContentLength="8192" maxNameTableCharCount="16384" 10 /> 11 </mtomMessageEncoding> 12 </binding> 13 </customBinding> 14 </bindings> 15 </system.serviceModel> 16 </configuration>