Message类型介绍
Message引用类型是WCF应用对SOAP消息的抽象。由于与SOAP消息如此紧密,因此Message类型定义了表示SOAP的版本、信封、消息头、消息头块和消息体元素的成员。从功能上讲,Message类型实际是对数据存储的一个包装,并且这个数据存储就是XML Infoset。
在整个生命周期里,传输到其他消息参与者之前,Message对象必须经过几次转换。从发送者角度来看,这个转换包含两步:序列化和编码。Message序列化是把Message实例化为XML Infoset,编码是把XML Infoset转换为特定的数据格式。从接收者角度来看,这个转换正好与发送者相反。
WCF XML栈
有三个关键的类型定义负责序列化和编码Message类型:XmlDictionary、XmlDictionaryWrite和XmlDictionaryReader。
XmlDictionary类型XmlDictionary对象是许多对key-value的映射。如字典和词汇表一样,XmlDictionary可以用简单的词语表示复杂的东西,并且不会丢失任何含义。在消息应用里,XmlDictionary有可能用来压缩经过序列化和编码的消息大小和编码过的消息大小,因此,可以减少需要发送消息占用的带宽。从内部来看,XmlDictionary定义了一个私有的可以表示SOAP消息中元素名字、属性和XML namespace声明的key-value对列表。XmlDictionary内部存储的key-value对是XmlDictionaryString类型的。XmlDictionaryString可以通过调用实例方法Add加入XmlDictionary里。Add方法接受一个String类型,返回一个XmlDictionaryString实例,如下所示:
XmlDictionary dictionary = new XmlDictionary();
List stringList = new List();
//增加元素名称到字典里,然后存储在StringList中
stringList.Add(dictionary.Add("ReleaseDate"));
stringList.Add(dictionary.Add("GoodSongs"));
stringList.Add(dictionary.Add("Studio"));
Console.WriteLine("entries in Collection:");
foreach(XmlDictionaryString entry in stringList)
{
Console.WriteLine("Key={0},Value={1}", entry.Key, entry.Value);
}
Console.ReadKey();
当执行前面代码时,可以看到Key属性的值自动被赋予每个XmlDictionaryString。
XmlDictionaryWrite类型
XmlDictionaryWrite类型是用来序列化和编码Message类型的,并且有时会使用XmlDictionary对象处理压缩工作。它继承自System.Xml.XmlWriter,因此继承了很多XmlWrite的属性。它定义了几个返回XmlDictionaryWrite子类型实例的工厂方法,包装了System.IO.Stream并定义了许多以Write单词开始的方法。这些方法大部分重载以下4个方法:CreateDictionaryWriter、CreateTextWriter、CreateMtomWriter和CreateBinaryWriter。
XmlDictionaryWriter类型上的工厂方法CreateDictionaryWrite就是接受XmlWrite类型的引用。内部来说,这些方法返回的实例都是简单地包装了传递的参数XmlWriter。
XmlDictionaryWriter类型定义了三种创建CreateTextWriter的工厂方法。这些工厂方法返回的是继承自XmlDictionaryWrite类型的实例,并且它们的作用是为了产生基于文本编码的XML。以下代码演示了如何使用CreateTextWriter方法:
MemoryStream stream = new MemoryStream();
//XmlDictionaryWrite 放在using结构里,因此可以确保Dispose方法被调用。
using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(stream, Encoding.UTF8, false))
{
writer.WriteStartDocument();
writer.WriteElementString("SongName","urn:ContosoRockabilia","Aqualung");
writer.Flush();
}
Console.WriteLine("XmlDictionaryWrite(Text-UTF8 wrote {0} bytes)",stream.Position);
stream.Position = 0;
Byte[] bytes = stream.ToArray();
Console.WriteLine(BitConverter.ToString(bytes));
//输出流中文本
Console.WriteLine("data read from stream:\n{0}\n", new StreamReader(stream).ReadToEnd());
当执行程序代码时,它会产生以下结果。
XmlDictionaryWrite定义了两种CreateMtomWrite方法。这些方法返回的是继承自XmlDictionaryWrite并产生MTOM编码的XML实例。两者都接收Stream类型的参数和几个别的控制XML Infoset编码的参数。这些参数设置编码格式、SOAP消息头的ContentType、多用途网络邮件扩展协议(MIME)的边界和MIME的统一资源标识符(URI),同时也包括是否把消息写入Stream对象。
XmlDictionaryWrite也定义了四种CreateBinaryWriter方法。这些方法返回的是继承自XmlDictionaryWriter并能产生二进制编码的XML实例。CreateBinaryWrite方法上的XmlBinaryWriterSession参数允许发送者和接收者自动采集和协调一个动态的XmlDictionary。为了在数据接收结束之后解码数据,接收者必须使用XmlBinaryReaderSession对象。XmlBinaryReaderSession对象会根据之前接收到的Steam对象中的dictionaey来自动组装自己。
XmlDictionaryReader类型
XmlDictionaryReader抽象类型继承自System.Xml.XMlReader,因此继承了很多XmlReader的特性。XmlDictionaryReader的工厂方法接收的参数和XmlDictionaryWriter的工厂方法几乎一一对应。这些参数与在XmlDictionaryWriter类型里的作用一样。
创建消息
可以选择众多CreateMessage工厂方法中来创建Message对象。这些方法绝大部分接收的都以SOAP消息体的内容作为参数。非常重要的一点是,Message的body在创建以后不能在修改。而SOAP消息头块在消息创建以后还可以增加和修改。
简要介绍Message序列化和反序列化
当发送程序需要发送一个Message到另一个消息参与者时,它必须创建包含适当信息的Message对象,然后序列化和编码Message到Stream或Byte状态(和发送者最后处理的数据状态一样)。接收程序必须解码和反序列化Stream 或Byte[]为Message对象,或许还要反序列化消息头部或消息体为其他对象。
Message版本
因为Message对象是CLR对SOAP消息的抽象,而使用的SOAP消息有多种版本,所以需要考虑Message对象所示的SOAP消息版本问题。当设置Message对象时,System.ServiceModel.Channels.EncelopeVersion类型代表了Message类型遵循的SOAP规范。同时,System.ServiceModel.Channels.AddressingVersion表示Message序列化时,消息头块遵循WS-Addressing规范。以下代码显示了MessageVersion的所有公开可见的成员:
// System.ServiceModel.Channels.MessageVersion
using System;
using System.ComponentModel;
using System.Globalization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
[TypeConverter(typeof(MessageVersionConverter))]
[__DynamicallyInvokable]
public sealed class MessageVersion
{
private EnvelopeVersion envelope;
private AddressingVersion addressing;
private static MessageVersion none;
private static MessageVersion soap11;
private static MessageVersion soap12;
private static MessageVersion soap11Addressing10;
private static MessageVersion soap12Addressing10;
private static MessageVersion soap11Addressing200408;
private static MessageVersion soap12Addressing200408;
[__DynamicallyInvokable]
public AddressingVersion Addressing
{
[__DynamicallyInvokable]
get
{
return this.addressing;
}
}
[__DynamicallyInvokable]
public static MessageVersion Default
{
[__DynamicallyInvokable]
get
{
return MessageVersion.soap12Addressing10;
}
}
[__DynamicallyInvokable]
public EnvelopeVersion Envelope
{
[__DynamicallyInvokable]
get
{
return this.envelope;
}
}
[__DynamicallyInvokable]
public static MessageVersion None
{
[__DynamicallyInvokable]
get
{
return MessageVersion.none;
}
}
[__DynamicallyInvokable]
public static MessageVersion Soap12WSAddressing10
{
[__DynamicallyInvokable]
get
{
return MessageVersion.soap12Addressing10;
}
}
public static MessageVersion Soap11WSAddressing10
{
get
{
return MessageVersion.soap11Addressing10;
}
}
public static MessageVersion Soap12WSAddressingAugust2004
{
get
{
return MessageVersion.soap12Addressing200408;
}
}
public static MessageVersion Soap11WSAddressingAugust2004
{
get
{
return MessageVersion.soap11Addressing200408;
}
}
[__DynamicallyInvokable]
public static MessageVersion Soap11
{
[__DynamicallyInvokable]
get
{
return MessageVersion.soap11;
}
}
public static MessageVersion Soap12
{
get
{
return MessageVersion.soap12;
}
}
static MessageVersion()
{
MessageVersion.none = new MessageVersion(EnvelopeVersion.None, AddressingVersion.None);
MessageVersion.soap11 = new MessageVersion(EnvelopeVersion.Soap11, AddressingVersion.None);
MessageVersion.soap12 = new MessageVersion(EnvelopeVersion.Soap12, AddressingVersion.None);
MessageVersion.soap11Addressing10 = new MessageVersion(EnvelopeVersion.Soap11, AddressingVersion.WSAddressing10);
MessageVersion.soap12Addressing10 = new MessageVersion(EnvelopeVersion.Soap12, AddressingVersion.WSAddressing10);
MessageVersion.soap11Addressing200408 = new MessageVersion(EnvelopeVersion.Soap11, AddressingVersion.WSAddressingAugust2004);
MessageVersion.soap12Addressing200408 = new MessageVersion(EnvelopeVersion.Soap12, AddressingVersion.WSAddressingAugust2004);
}
private MessageVersion(EnvelopeVersion envelopeVersion, AddressingVersion addressingVersion)
{
this.envelope = envelopeVersion;
this.addressing = addressingVersion;
}
[__DynamicallyInvokable]
public static MessageVersion CreateVersion(EnvelopeVersion envelopeVersion)
{
return MessageVersion.CreateVersion(envelopeVersion, AddressingVersion.WSAddressing10);
}
[__DynamicallyInvokable]
public static MessageVersion CreateVersion(EnvelopeVersion envelopeVersion, AddressingVersion addressingVersion)
{
if (envelopeVersion == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("envelopeVersion");
}
if (addressingVersion == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("addressingVersion");
}
if (envelopeVersion == EnvelopeVersion.Soap12)
{
if (addressingVersion == AddressingVersion.WSAddressing10)
{
return MessageVersion.soap12Addressing10;
}
if (addressingVersion == AddressingVersion.WSAddressingAugust2004)
{
return MessageVersion.soap12Addressing200408;
}
if (addressingVersion == AddressingVersion.None)
{
return MessageVersion.soap12;
}
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("addressingVersion", SR.GetString("AddressingVersionNotSupported", addressingVersion));
}
if (envelopeVersion == EnvelopeVersion.Soap11)
{
if (addressingVersion == AddressingVersion.WSAddressing10)
{
return MessageVersion.soap11Addressing10;
}
if (addressingVersion == AddressingVersion.WSAddressingAugust2004)
{
return MessageVersion.soap11Addressing200408;
}
if (addressingVersion == AddressingVersion.None)
{
return MessageVersion.soap11;
}
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("addressingVersion", SR.GetString("AddressingVersionNotSupported", addressingVersion));
}
if (envelopeVersion == EnvelopeVersion.None)
{
if (addressingVersion == AddressingVersion.None)
{
return MessageVersion.none;
}
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("addressingVersion", SR.GetString("AddressingVersionNotSupported", addressingVersion));
}
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("envelopeVersion", SR.GetString("EnvelopeVersionNotSupported", envelopeVersion));
}
[__DynamicallyInvokable]
public override bool Equals(object obj)
{
return this == obj;
}
[__DynamicallyInvokable]
public override int GetHashCode()
{
int num = 0;
if (this.Envelope == EnvelopeVersion.Soap11)
{
num++;
}
if (this.Addressing == AddressingVersion.WSAddressingAugust2004)
{
num += 2;
}
return num;
}
[__DynamicallyInvokable]
public override string ToString()
{
return SR.GetString("MessageVersionToStringFormat", this.envelope.ToString(), this.addressing.ToString());
}
internal bool IsMatch(MessageVersion messageVersion)
{
if (messageVersion == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("messageVersion");
}
if (this.addressing == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "MessageVersion.Addressing cannot be null")));
}
if (this.envelope != messageVersion.Envelope)
{
return false;
}
if (this.addressing.Namespace != messageVersion.Addressing.Namespace)
{
return false;
}
return true;
}
}
对象序列化图
CreateMessage方法是用来设计序列化对象到Message消息体中的。为了达到这个目的,这些方法要接收object类型的参数。其中一种方法是使用WCF默认的序列化器,另一种方法是接受自定义序列化器。此外,除了这些参数外,还有接受String类型的参数。这个参数,在相关的Message对象的头块中设置WS-Affressing Action的值。
从Reader提取数据
CreateMessage方法接受XmlReader或XmlDictionaryReader作为参数。这些方法会拉出XmlDictionaryReader的整个内容到返回的Message对象里,或者到Message的body(消息体)里。CreateMessage方法被重载为多种方法,它们包含了读取整个消息和只读取消息体的参数。
const int MAXHEADERSIZE = 500;
//读取信封的例子
//从包含消息的文件里读取数据
FileStream stream = File.Open("entireMessage.xml", FileMode.Open);
XmlDictionaryReader envelopeReader = XmlDictionaryReader.CreateTextReader(stream,new XmlDictionaryReaderQuotas());
Message msg = Message.CreateMessage(envelopeReader, MAXHEADERSIZE, MessageVersion.Soap11WSAddressing10);
Console.WriteLine("{0}\n",msg.ToString());
//读取消息体的例子
//从文件里只读消息体数据
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.ServieModel.Channels.BodyWrter把数据推送到Message里。BodyWriter是一个抽象类型,它展示了接受XmlDictionaryWrite作为参数的OnWriteBodyContents包含方法。正是通过这种方法,BodyWrite的子类型可以对Message的创建过程产生影响,因此BodyWrite对影响消息的反序列化很有用。以下演示了BodyWriter子类型是如何从XML文件里读取数据并放入Message的消息体里的:
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 reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
reader.ReadStartElement();
while(reader.NodeType!= XmlNodeType.EndElement)
{
writer.WriteNode(reader,true);
}
}
}
}
Message和SOAP Fault
Message类型定义了用来创建表示SOAP Fault的消息对象的工厂方法。SOAP Fault是SOAP消息的一种形式,用来表示错误信息。
Buffered vs Streamed消息
当考虑消息在端点之间流动时,就会本能地想到缓存。换句话说,假设当程序接收到一个Message时,它已经知道了整个Message,这种方式称为缓存模式(buffering)。与之相对的就是流处理模式,它有两种流处理模式。第一种为推模型,指发送者按照自己的节奏推送字节流到接收者。当发送流数据时,发送者会把数据写到本地缓冲区直到写满为止,并且数据会发送给接收者,当数据到达时,接收者会从本地缓冲区读取数据。第二种称为拉模型。当发送流数据时,接收者会重复执行直到请求的数据发送完毕。WCF基础结构实现了第二种流处理方法。
序列化消息
Message类型上的所有序列化方法名称都以Write开始的,而且这些方法都接受XmlWriter或XmlDictionaryWrite让类型的参数。消息的的实际序列化工作由XmlWriter或XmlDictionaryWriter对象完成,而不是直接由Message对象完成。Message类型同样定义了对于消息序列化进行粒度控制的方法,例如,WriteBody方法序列化body标签和元素到XmlWriter或XmlDictionaryWriter包装的Stream里。换句话说,WriteBodyContents方法序列化Body元素(没有Body标签)到XmlDictionaryWriter包装的Stream里。可序列化方法原型如下:
void WriteStartEnvelope(XmlDictionaryWriter writer);
void WriteStartBody(XmlDictionaryWriter writer);
void WriteStartBody(XmlWriter writer);
void WriteBody(XmlDictionaryWriter writer);
void WriteBody(XmlWriter writer);
void WriteBodyContents(XmlWriter writer);
void WriteMessage(XmlDictionaryWriter writer);
void WriteMessage(XmlWriter writer);
反序列化消息
在接收程序里普遍存在的任务就是消息的反序列化。消息的反序列化是从一个序列化的消息中创建一个新的消息别称。泛型方法GetBody允许调用者反序列化消息体的内容到T类型的对象里。另一种方法接受一个XmlObjectSerializer参数,因此为消息体的反序列化提供扩展点。可序列化方法原型如下:
public T GetBody();
public T GetBody(XmlObjectSerializer serializer);
Message状态
Message类型是多状态的,Message状态可以通过多种方式描述。和任何一个引用类型一样,Message状态是它的各个字段的组合。Message的属性State表示MessageState的一个私有字段值。MessageState是一个枚举类型,它定义了5个值:Created、Read、Written、Copied和Cloased。Message对象的数State的值会随着该对象方法的调用而改变。从内部来看,Message通过State属性管理Message对象上方法调用的顺序。
使用消息头
消息头块被SOAP消息基础结构用来表示地址、路由和安全信息。WCF也完全支持SOAP的消息处理结构,它包含一些创建、序列化和分析SOAP消息头块的工具。Message类型是一个SOAP消息的CLR抽象,它定义的成员允许WCF基础结构使用发送或接收到的消息头块。Message类型的Headers属性提供了这项功能。MessageHeader是对SOAP消息头块的泛型CLR抽象,从广义上说,MessageHeaders是一组MessageHeader对象;EndpointAddress是对WS-Addressing endpoint规范的CLR抽象。
复制消息
有时需要从现有的消息实例创建一个缓存模式的消息拷贝。创建Message的拷贝还是相当简单的,但是会带来消息内部状态的改变。如果使用不当,状态改变会给要复制的消息对象带来一些问题。当调用CreateBufferedCopy方法时,新消息的state属性必须为MessageState.Created。如果设置其他状态,该方法就会抛出一个IncalidOperationExecption异常。直到CreateBufferedCopy返回结果,原调用实例的状态才会变为MessageState.Copied。如果该方法调用成功,则会返回一个System.ServiceModel.channels.MessageBuffer类型的实例。这个实例的状态会是Message.Created。以下代码演示了如何复制一个消息:
Message msg = Message.CreateMessage(MessageVersion.Default, "urn:SomeAction", "Something in the body");
MessageBuffer buffer = msg.CreateBufferedCopy(int.MaxValue);
Message msgNew = buffer.CreateMessage();
清理消息
Message类型实现了IDisposable接口,并且定义了一个Close方法。