创建一个消息
可以选择众多定义的CreateMessage工厂方法中的一个来创建Message对象。绝大部分,这些方法接受的都是
SOAP消息体的内容作为参数。非常重要的一点是Message的body在创建以后就不能再做修改。SOAP消息头块,话句话说,在消息创建以后还可以增加和修改。
一般地说,Message类型的工厂方法可以根据处理消息的路线图来划分的,从
XmlReader提取数据的方法,把数据放进Message的方法,和产生表示SOAP Fault
Message的方法。在我们研究各个不同分类的工厂方法之前,先看一些Message序列化和反序列化的上下文环境。
简要介绍Message序列化和反序列化
序列化和反序列化在分布式计算里很常见,因此,当它们牵扯到消息应用系统的时候,就有必要弄清楚他们的意义。让我们考虑一下基本的序列化和反序列化Message对象的步骤。放发送程序需要发送一个Message到另外一个消息参与者的时候,它必须创建一个包含适当信息的
Message对象,然后序列化和编码Message到一个Stream或Byte里,最后发送Stream或Byte到目的地。当接收程序收到Message的时候,一般来说,它还是Stream或Byte状态(和发送者最后处理的数据状态一样)。接收程序必须解码和反序列化Stream或Byte[]为Message对象,或许还要反序列化消息头部或者消息体为其它对象。
正如你看到的,序列化通常是与发送者规定的任务相关的,反序列化通常和接收者的任务相关。发送者和接收者都要创建Message
对象,但是发送者是内存里的其它对象创建Message对象,而接受者是通过解码和反序列
Stream 或 Byte来创建一个
Message对象。一旦接收程序解码和反序列Stream 或 Byte为一个
Message对象,它就可以转换消息的内容为一个对象,或者经过多次的对象转换。这个转换,一般来说,也是另外一种形式的反序列化。
Message版本
因为Message对象是
CLR对SOAP消息的抽象,而使用的SOAP消息有多种版本,所以就需要考虑Message对象的表示的SOAP消息版本问题。
在
Message对象模型里,SOAP消息的版本一旦在创建时设定,就无法改变。SOAP和WS-*规范在不断的变化,因此,随着时间推移,我们应该希望这些文档版本更新。随着它们的改变,namespace和消息结构变化也在情理之中。为了适应这些必然发生的变化,WCF提供了几个封装SOAP规范和WS-*规范XML消息语义的类型。这些类型的实例会传递给工厂方法来表示Message对象的版本,Message上定义大部分工厂方法都接受这些类型的参数。
当设置Message对象时,
System.ServiceModel.Channels.EnvelopeVersion类型代表了一个Message类型遵循的SOAP规范。同时,System.ServiceModel.
Channels.AddressingVersion表示
Message序列化时,消息头块遵循的WS-Addressing规范。在WCF一次发布的时候,有2个SOAP
老徐备注2规范(1.1 和1.2),WS-Addressing规范(2004年8月,1.0)。
System.ServiceModel.Channels.MessageVersion包装了
EnvelopeVersion和AddressingVersion类型。MessageVersion有几个static属性,它们表示EnvelopeVersion和AddressingVersion的几种可能的组合。下面代码显示了MessageVersion的所有公开可见的成员:
namespace System.ServiceModel.Channels {
public sealed class MessageVersion {
public AddressingVersion Addressing { get; }
public static MessageVersion Default { get; }
public EnvelopeVersion Envelope { get; }
public static MessageVersion Soap11 { get; }
public static MessageVersion Soap12 { get; }
public static MessageVersion None { get; }
public static MessageVersion Soap11WSAddressing10 { get; }
public static MessageVersion Soap11WSAddressingAugust2004 { get; }
public static MessageVersion Soap12WSAddressing10 { get; }
public static MessageVersion Soap12WSAddressingAugust2004 { get; }
public static MessageVersion CreateVersion(
EnvelopeVersion envelopeVersion);
public static MessageVersion CreateVersion(
EnvelopeVersion envelopeVersion,
AddressingVersion addressingVersion);
public override bool Equals(object obj);
public override string ToString();
}
}
public sealed class MessageVersion {
public AddressingVersion Addressing { get; }
public static MessageVersion Default { get; }
public EnvelopeVersion Envelope { get; }
public static MessageVersion Soap11 { get; }
public static MessageVersion Soap12 { get; }
public static MessageVersion None { get; }
public static MessageVersion Soap11WSAddressing10 { get; }
public static MessageVersion Soap11WSAddressingAugust2004 { get; }
public static MessageVersion Soap12WSAddressing10 { get; }
public static MessageVersion Soap12WSAddressingAugust2004 { get; }
public static MessageVersion CreateVersion(
EnvelopeVersion envelopeVersion);
public static MessageVersion CreateVersion(
EnvelopeVersion envelopeVersion,
AddressingVersion addressingVersion);
public override bool Equals(object obj);
public override string ToString();
}
}
大部分成员都是可以自描述的;一些需要解释。在WCF版本3里,
MessageVersion.Default返回了等价的MessageVersion.Soap12WSAddressing10的static属性。正如你从名字里看的,这个属性表示它遵守SOAP 1.2 和WS-Addressing 1.0规范。
MessageVersion.Soap11 和
MessageVersion.Soap12属性给AddressingVersion赋值为AddressingVersion.None,并且根据属性名字给EnvelopeVersion赋值。当你想创建一个发送SOAP消息,但是不想实现WS-Addressing规范的程序时,这个非常有用。和你想的一样,
MessageVersion.None对于(朴树的旧的XML)POX消息场景非常有用。
|
重点
|
当任何一个规范的发展(不可避免的),在随后的WCF版本里,使用MessageVersion.Default构建
Message的代码也会悄悄地变化。这样也好也坏,取决于消息场景,例如,修改WCF的版本或许也要修改MessageVersion.Default属性产生的XML相关构件。如果修改真的发生,所有使用新的消息交互的消息参与者都必须知道新的消息语义。换句话说,一个使用MessageVersion.Default属性的程序或许通过升级WCF带来巨大的变化,因此,MessageVersion.Default属性应该谨慎使用。
|
下面代码展示了
MessageVersion
老徐备注1类型引用的SOAP和WS-Addressing版本:
using System;
using System.ServiceModel.Channels;
using System.Xml;
class Program {
static void Main(string[] args){
MessageVersion version = MessageVersion.Default;
PrintMessageVersion("Default",version);
version = MessageVersion.Soap11WSAddressing10;
PrintMessageVersion("Soap11WSAddressing10",version);
version = MessageVersion.Soap11WSAddressingAugust2004;
PrintMessageVersion("Soap11WSAddressingAugust2004",version);
version = MessageVersion.Soap12WSAddressing10;
PrintMessageVersion("Soap12WSAddressing10",version);
version = MessageVersion.Soap12WSAddressingAugust2004;
PrintMessageVersion("Soap12WSAddressingAugust2004", version);
}
private static void PrintMessageVersion(String name,
MessageVersion version) {
Console.WriteLine("Name={0}\nEnvelope={1}\nAddressing={2}\n",
name,
version.Envelope.ToString(),
version.Addressing.ToString());
}
}
using System.ServiceModel.Channels;
using System.Xml;
class Program {
static void Main(string[] args){
MessageVersion version = MessageVersion.Default;
PrintMessageVersion("Default",version);
version = MessageVersion.Soap11WSAddressing10;
PrintMessageVersion("Soap11WSAddressing10",version);
version = MessageVersion.Soap11WSAddressingAugust2004;
PrintMessageVersion("Soap11WSAddressingAugust2004",version);
version = MessageVersion.Soap12WSAddressing10;
PrintMessageVersion("Soap12WSAddressing10",version);
version = MessageVersion.Soap12WSAddressingAugust2004;
PrintMessageVersion("Soap12WSAddressingAugust2004", version);
}
private static void PrintMessageVersion(String name,
MessageVersion version) {
Console.WriteLine("Name={0}\nEnvelope={1}\nAddressing={2}\n",
name,
version.Envelope.ToString(),
version.Addressing.ToString());
}
}
运行代码产生以下输出:
Name=Default
Envelope=Soap12 (http://www.w3.org/2003/05/soap-envelope)
Addressing=Addressing10 (http://www.w3.org/2005/08/addressing)
Name=Soap11WSAddressing10
Envelope=Soap11 (http://schemas.xmlsoap.org/soap/envelope/)
Addressing=Addressing10 (http://www.w3.org/2005/08/addressing)
Name=Soap11WSAddressingAugust2004
Envelope=Soap11 (http://schemas.xmlsoap.org/soap/envelope/)
Addressing=Addressing200408 (http://schemas.xmlsoap.org/ws/2004/08/addressing)
Name=Soap12WSAddressing10
Envelope=Soap12 (http://www.w3.org/2003/05/soap-envelope)
Addressing=Addressing10 (http://www.w3.org/2005/08/addressing)
Name=Soap12WSAddressingAugust2004
Envelope=Soap12 (http://www.w3.org/2003/05/soap-envelope)
Addressing=Addressing200408
(http://schemas.xmlsoap.org/ws/2004/08/addressing)
Envelope=Soap12 (http://www.w3.org/2003/05/soap-envelope)
Addressing=Addressing10 (http://www.w3.org/2005/08/addressing)
Name=Soap11WSAddressing10
Envelope=Soap11 (http://schemas.xmlsoap.org/soap/envelope/)
Addressing=Addressing10 (http://www.w3.org/2005/08/addressing)
Name=Soap11WSAddressingAugust2004
Envelope=Soap11 (http://schemas.xmlsoap.org/soap/envelope/)
Addressing=Addressing200408 (http://schemas.xmlsoap.org/ws/2004/08/addressing)
Name=Soap12WSAddressing10
Envelope=Soap12 (http://www.w3.org/2003/05/soap-envelope)
Addressing=Addressing10 (http://www.w3.org/2005/08/addressing)
Name=Soap12WSAddressingAugust2004
Envelope=Soap12 (http://www.w3.org/2003/05/soap-envelope)
Addressing=Addressing200408
(http://schemas.xmlsoap.org/ws/2004/08/addressing)
序列化对象
几个
CreateMessage方法是设计来序列化一个对象到Message消息体里的。
为了这个目的,这些方法要接受System.Object类型的参数。其中一个方法使用WCF默认的序列化器,并且另外一个接受自定义序列化器。(我们会在第9章:契约,里详细讨论)此外,除了这些参数,这些方法还接受一个String类型的参数。这个参数,在相关的Message对象的头块里设置WS-Addressing Action的值。如下所示,这些工厂方法相当简单:
// pass a String into the factory method
Message msg = Message.CreateMessage(MessageVersion.Soap12WSAddressing10,
"urn:SomeAction",
"Hello There");
// the ToString() method returns the entire Message
Console.WriteLine(msg.ToString());
Message msg = Message.CreateMessage(MessageVersion.Soap12WSAddressing10,
"urn:SomeAction",
"Hello There");
// the ToString() method returns the entire Message
Console.WriteLine(msg.ToString());
运行代码产生以下输出:
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">urn:SomeAction</a:Action>
</s:Header>
<s:Body>
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
Hello There
</string>
</s:Body>
</s:Envelope>
xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">urn:SomeAction</a:Action>
</s:Header>
<s:Body>
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
Hello There
</string>
</s:Body>
</s:Envelope>
让我们修改Object从String参数到PurchaseOrder,如下所示,看看会发生什么:
sealed class MyApp {
static void Main() {
VendorInfo vInfo = new VendorInfo(5, "Contoso");
PurchaseOrder po = new PurchaseOrder(50, vInfo);
Message msg = Message.CreateMessage(
MessageVersion.Soap12WSAddressing10,
"urn:SomeAction",
po);
// the ToString() method returns the entire Message 。ToString()方法返回整个Message
Console.WriteLine(msg.ToString());
}
private sealed class PurchaseOrder {
Int32 poNumber;
VendorInfo vendorInfo;
internal PurchaseOrder(Int32 poNumber, VendorInfo vendorInfo) {
this.poNumber = poNumber;
this.vendorInfo = vendorInfo;
}
}
private sealed class VendorInfo {
Int32 vendorNumber;
String vendorName;
internal VendorInfo(Int32 vendorNumber, String vendorName) {
this.vendorNumber = vendorNumber;
this.vendorName = vendorName;
}
}
}
static void Main() {
VendorInfo vInfo = new VendorInfo(5, "Contoso");
PurchaseOrder po = new PurchaseOrder(50, vInfo);
Message msg = Message.CreateMessage(
MessageVersion.Soap12WSAddressing10,
"urn:SomeAction",
po);
// the ToString() method returns the entire Message 。ToString()方法返回整个Message
Console.WriteLine(msg.ToString());
}
private sealed class PurchaseOrder {
Int32 poNumber;
VendorInfo vendorInfo;
internal PurchaseOrder(Int32 poNumber, VendorInfo vendorInfo) {
this.poNumber = poNumber;
this.vendorInfo = vendorInfo;
}
}
private sealed class VendorInfo {
Int32 vendorNumber;
String vendorName;
internal VendorInfo(Int32 vendorNumber, String vendorName) {
this.vendorNumber = vendorNumber;
this.vendorName = vendorName;
}
}
}
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">urn:SomeAction</a:Action>
</s:Header>
<s:Body>
<MyApp.PurchaseOrder
xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns=
"http://schemas.datacontract.org/2004/07/CreatingMessageBySerialization">
<poNumber>50</poNumber>
<vendorInfo>
<vendorName>Contoso</vendorName>
<vendorNumber>5</vendorNumber>
</vendorInfo>
</MyApp.PurchaseOrder>
</s:Body>
</s:Envelope>
xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">urn:SomeAction</a:Action>
</s:Header>
<s:Body>
<MyApp.PurchaseOrder
xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns=
"http://schemas.datacontract.org/2004/07/CreatingMessageBySerialization">
<poNumber>50</poNumber>
<vendorInfo>
<vendorName>Contoso</vendorName>
<vendorNumber>5</vendorNumber>
</vendorInfo>
</MyApp.PurchaseOrder>
</s:Body>
</s:Envelope>
从Reader提取数据
几个
CreateMessage方法接受一个XmlReader或XmlDictionaryReader作为参数。这些方法“pull” XmlDictionaryReader的整个内容到返回的Message对象里,或者到Message的body里(消息体)。着重指出的是CreateMessage方法接受一个XmlReader作为参数,通过调用XmlDictionaryReader 的CreateDictionaryReade工厂方法来创建一个XmlDictionaryReader对象。
这些方法非常有用,当你要把
Message反序列化到一个Byte[] 或者Stream里的时,这是特别常见的,尤其是当接收程序接受一个包含序列化和编码过的数据的Stream时。当你使用这些方法构建一个Message对象,你必须知道Byte[]或Stream是包含了全部消息还是仅仅是消息体。为了适应2种情况,CreateMessag方法被重载为多个方法,他们包含了能读取整个消息和只读取消息体的参数。
为了说明问题,下面的文件包含了已经被序列化到entireMessage.xml文件里的消息内容:
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/
05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">urn:SomeAction</a:Action>
</s:Header>
<s:Body>
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
Hello Message
</string>
</s:Body>
</s:Envelope>
05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">urn:SomeAction</a:Action>
</s:Header>
<s:Body>
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
Hello Message
</string>
</s:Body>
</s:Envelope>
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
Hello Message
</string>
Hello Message
</string>
const Int32 MAXHEADERSIZE = 500;
// ENVELOPE READER EXAMPLE
// Get data from the file that contains the entire message
FileStream stream = File.Open("entireMessage.xml", FileMode.Open);
XmlDictionaryReader envelopeReader =
XmlDictionaryReader.CreateTextReader(stream, new
XmlDictionaryReaderQuotas());
Message msg = Message.CreateMessage(envelopeReader,
MAXHEADERSIZE,
MessageVersion.Soap12WSAddressing10);
Console.WriteLine("{0}\n", msg.ToString());
//BODY READER EXAMPLE消息体读取的例子
// Get data from a file that contains just the body从文件里只读消息体数据
stream = File.Open("bodyContent.xml", FileMode.Open);
XmlDictionaryReader bodyReader =
XmlDictionaryReader.CreateTextReader(stream, new
XmlDictionaryReaderQuotas());
msg = Message.CreateMessage(MessageVersion.Soap12WSAddressing10,
"urn:SomeAction", bodyReader);
Console.WriteLine("{0}\n", msg.ToString());
// ENVELOPE READER EXAMPLE
// Get data from the file that contains the entire message
FileStream stream = File.Open("entireMessage.xml", FileMode.Open);
XmlDictionaryReader envelopeReader =
XmlDictionaryReader.CreateTextReader(stream, new
XmlDictionaryReaderQuotas());
Message msg = Message.CreateMessage(envelopeReader,
MAXHEADERSIZE,
MessageVersion.Soap12WSAddressing10);
Console.WriteLine("{0}\n", msg.ToString());
//BODY READER EXAMPLE消息体读取的例子
// Get data from a file that contains just the body从文件里只读消息体数据
stream = File.Open("bodyContent.xml", FileMode.Open);
XmlDictionaryReader bodyReader =
XmlDictionaryReader.CreateTextReader(stream, new
XmlDictionaryReaderQuotas());
msg = Message.CreateMessage(MessageVersion.Soap12WSAddressing10,
"urn:SomeAction", bodyReader);
Console.WriteLine("{0}\n", msg.ToString());
使用BodyWriter把数据放进Message。
其中一个
CreateMessage的重载方法允许调用者使用System.ServiceModel.Channels.BodyWriter 把数据“push”到Message里。BodyWriter是一个抽象类型,它暴露了一个接受XmlDictionaryWriter作为参数的保护方法。BodyWriter的一个子类型完全控制Message 消息体的创建过程,因此BodyWriter对于控制Message的反序列化非常有用。绝大部分上,OnWriteBodyContents方法的实现包含调用XmlDictionaryWriter参数上的不同的Write方法。下面的例子演示了BodyWriter子类型如何从XML文件里读取数据并放进Message的消息体里:
sealed class MyBodyWriter : BodyWriter {
private String m_fileName;
internal MyBodyWriter(String fileName) : base(true) {
this.m_fileName = fileName;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer) {
using (FileStream stream = File.Open(m_fileName, FileMode.Open)) {
XmlDictionaryReader reader1 =
XmlDictionaryReader.CreateTextReader(stream, new
XmlDictionaryReaderQuotas());
reader1.ReadStartElement();
while (reader1.NodeType != XmlNodeType.EndElement) {
writer.WriteNode(reader1, true);
}
}
}
}
internal MyBodyWriter(String fileName) : base(true) {
this.m_fileName = fileName;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer) {
using (FileStream stream = File.Open(m_fileName, FileMode.Open)) {
XmlDictionaryReader reader1 =
XmlDictionaryReader.CreateTextReader(stream, new
XmlDictionaryReaderQuotas());
reader1.ReadStartElement();
while (reader1.NodeType != XmlNodeType.EndElement) {
writer.WriteNode(reader1, true);
}
}
}
}
Message pushMessage = Message.CreateMessage(
MessageVersion.Soap12WSAddressing10,
"urn:SomeAction",
new MyBodyWriter("bodyContent.xml"));
MessageVersion.Soap12WSAddressing10,
"urn:SomeAction",
new MyBodyWriter("bodyContent.xml"));
老徐备注
1. MessageVersion在.NET4.0里也没什么变化。可以使用反射工具查看。
2. SOAP版本:
(1) From SOAP/1.1 to SOAP Version 1.2 in 9 points:http://www.w3.org/2003/06/soap11-soap12.html
SOAP1.2相对SOAP1.1的改进:更清晰、更好的与Web集成、更强功能、更快速
l 更清晰: 清晰的处理和扩展模型,更好的互操作性。
l 更好的与Web集成:更好地与XML standards和 Web架构集成。
l 更强功能:协议无关.
l 更快速: 基于XML Infoset,允许优化。
(2) Simple Object Access Protocol (SOAP) 1.1:http://www.w3.org/TR/2000/NOTE-SOAP-20000508/
(3) SOAP Version 1.2:http://www.w3.org/TR/soap12-part1/
本文转自 frankxulei 51CTO博客,原文链接:http://blog.51cto.com/frankxulei/318591,如需转载请自行联系原作者