Buffered vs. Streamed消息
当我们在终结点之间流动的消息时,我们会本能地想到缓存。
换个方式来说,我们假设程序接收到一个Message
时,它已经知道整个Message
。这种方式称作缓存模式
(buffering)。与之相对的就是流处理模式(
streaming),并且有2种流处理模式(streaming)。第一种是推模型(push model),发送者按照自己的节奏推送字节流到接收者。当流数据发送的时候,发送者把数据写到本地缓存直到写满为止,数据会发生给接收者,接收者会从本地缓冲区读取数据。第二种机制成为拉模型(pull model)。当流数据发送的时候,接收者从发送者请求数据,在收到请求以后,发送者发送请求的数据。这个过程会重复执行知道请求的数据发送完毕。WCF基础结构实现了第二种流处理方法。
在WCF里,Message的消息头块通常是缓存起来的,消息内容可以buffered或者streamed模式。缓存区的大小默认是64KB.(你会在第8章里了解如何改变这个设置。)如果消息体是streamed模式,它的大小就是无限制的。实际上,这意味着我们可以在WCF里传递流媒体。不是所有的消息都是流处理消息体元素。例如,小的消息就不需要streamed模式;缓存模式会更加高效地处理它们。更确切地说,一个大的消息本质上验证是十分困难的。想一下,一个例子,假如使用streamed模式发送一个30分钟长的电影作为消息体的消息。电影非常不错,并且可以在接收完毕前就可以播放给观众。如果数据流终止,并且没任何发送结束标记,处理错误就变得不太可能了,因为用户可能已经看到数据了,同样地,如果一个程序已经对数据做了数据签名,这个签名只能在整个数据流结构和缓存以后才能验证了,这就不适合使用streamed模式处理消息体数据。
序列化消息
既然你已经学习了如何创建消息,现在我们来研究一下如何序列化消息的全部或者某一部分。首先,Message类型上的所有序列化方法名称都是以
Write开始,而且这些方法都接受XmlWriter或者XmlDictionaryWriter类型的参数。消息的实际序列化工作由
XmlWriter或者XmlDictionaryWriter对象完成,而不是直接由
Message对象完成。记得前面关于XmlDictionaryWriter的讨论,实际的序列化包括消息序列化和编码2个步骤。序列化小的方法原型如下:
public void WriteStartEnvelope(XmlDictionaryWriter writer);
public void WriteStartBody(XmlDictionaryWriter writer);
public void WriteStartBody(XmlWriter writer);
public void WriteBody(XmlDictionaryWriter writer);
public void WriteBody(XmlWriter writer);
public void WriteBodyContents(XmlDictionaryWriter writer);
public void WriteMessage(XmlDictionaryWriter writer);
public void WriteMessage(XmlWriter writer);
WriteMessage方法序列化消息的全部内容到
XmlWriter或者XmlDictionaryWriter包装的
Stream里。因为这些方法序列化整个消息,因此与其它方法相比,它们的使用频率最高。
Message类型同样定义了对于消息序列化进行粒度控制的方法。例如,
WriteBody方法序列化body标签和元素到XmlWriter或者XmlDictionaryWriter包装的
Stream里。WriteBodyContents方法,话句话说,序列化body元素(没有body标签)到XmlDictionaryWriter包装的
Stream里。WriteStartEvelope方法简单的写<s:Envelope标签到到XmlDictionaryWriter包装的
Stream里。在WriteStartEnvelope之后立即调用WriteStartBody方法,会写XML namespace到envelope,并且序列化body开始标签,从序列化内容完全忽略消息头。实际上,如果我们需要在使用这些方法的时候对消息序列化做额外的控制,我们肯定想序列化消息头的内容。这个功能隐含在Message对象模型里,并且会在本章后面的“Message Headers类型”一节里做相应的介绍。记住,如果你想手工序列化一个消息,你必须明确序列化消息头块。还没有明确的方法可以写envelope或body的结束标签。但是,为了写envelope或body的结束标签,我们直接调用XmlWriter.
WriteEndElement方法。
反序列化消息
在接受程序里普遍存在的一个任务就是消息的反序列化。消息的反序列化是从一个序列化的消息创建一个新的消息的别称。因为我们已经过了如何创建一个Message对象,同样也讲了大部分的消息反序列化的内容。更确切地说,我们已经学习了如何使用
XmlDictionaryReader类型从一个Stream或者Byte创建一个Message。
想一下我们关于Message工厂方法的讨论,其中一种创建消息体的方法就是传递
Object给工厂方法。同样的方式,我们或许需要从一个Message实例反序列化Object。为了这个目的,Message类型定义了从Message对象反序列化消息体的成员。这些方法的原型如下;
public T GetBody<T>();
public T GetBody<T>(XmlObjectSerializer serializer);
泛型方法
GetBody允许调用者反序列化消息体的内容到T类型的对象里。另外一个方法接受一个XmlObjectSerializer参数,因此为消息体的反序列化提供了扩展点。不论我们调用哪个方法,我们必须知道Message的消息体内包含的类型信息。如果我们泛型方法里使用的类型和消息体的类型不兼容,就会抛出一个SerializationException异常。
检查Message 是否是SOAP Fault
正如你看到的,Message类型的实例表示一个SOAP 消息或者SOAP Fault。当接受程序反序列化一个Message的时候,它必须能够确定这个
Message是否是一个SOAP Fault,因为通常SOAP 消息和SOAP Fault的执行路径不同。因此,Message定义了一个只读属性
IsFault。简而言之,一旦一个Message对象从进来的Stream or
Byte反序列化完毕,
IsFault属性会标记Message是否是一个SOAP Fault,这也是WCF基础结构对于反序列化的消息作出的第一次检查。我们可以通过改变前面代码里CreateAndShowMessage方法来演示这个属性的功能,如下所示;
private static void CreateAndShowMessage(MessageFault messageFault,
MessageVersion version) {
Message message = Message.CreateMessage(version,
messageFault,
"urn:SomeFaultAction");
// commented out for clarity
// Console.WriteLine("{0}\n", message.ToString());
// ** New code begins here **
MemoryStream stream = new MemoryStream();
// write the Message to a Stream
XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(
stream,null, null, false);
message.WriteMessage(writer);
writer.Flush();
stream.Position = 0;
// read the Message from the Stream从Stream读取消息
XmlDictionaryReader reader =
XmlDictionaryReader.CreateBinaryReader(stream, new
XmlDictionaryReaderQuotas());
message = Message.CreateMessage(reader, Int32.MaxValue, version);
// check if it is a Fault检查消息是否是SOAP Fault
Console.WriteLine("the message {0} a SOAP Fault",
message.IsFault ? "is" : "is not");
}
当这些代码执行的时候(像前面的代码),产生下面的输出:
the message is a SOAP Fault
the message is a SOAP Fault
注意到对于连个创建对象Message.IsFault属性都返回
true,这里着重指出的是对于所有的表示SOAP Fault的Message对象,
Message.IsFault属性都返回true,不管消息数据的编码和版本是什么。