《WCF技术内幕》翻译27:第2部分_第5章_消息:使用消息头(上)

  1. 云栖社区>
  2. 博客>
  3. 正文

《WCF技术内幕》翻译27:第2部分_第5章_消息:使用消息头(上)

技术小胖子 2017-11-14 18:36:00 浏览565
展开阅读全文
(目前翻译的是MessageHeader部分)。

使用消息头

正如你在第二章里看到的一样,消息头块被SOAP消息基础结构用来表示地址、路由和安全信息。因为WCF也是一个完全支持SOAP的消息处理基础结构,它包含一些创建、序列化和分析SOAP消息头块的工具。记住Message类型是一个SOAP消息的CLR抽象,它定义的成员允许WCF基础结构使用发送或接受到的消息头块。Message 类型的Headers属性提供了这个功能。和WCF里其它关键的类型一样,使用Headers属性需要我在WCF API里与其它类型交互,即MessageHeader、MessageHeaders和EndpointAddress 类型。这些类型的名字已经暗示了他们的作用。例如,MessageHeader是对SOAP消息头块的泛型CLR抽象;MessageHeaders,广义上说,是一组MessageHeader对象;EndpointAddress是对WS-Addressing endpoint规范的CLR抽象。具体使用的时候,这些类型提供了给Message插入、序列化、编码、反序列化、解码消息头块,以及从反序列化的消息头块里提取信息的能力。本节,我们将会研究这些基本类型,以及如何和Message一起使用。

MessageHeader类型

WCF里SOAP消息头的基本构建单位就是MessageHeader类型,它的对象模型和Message类型很像。与Message一样,MessageHeader是抽象类,它暴露了几个返回MessageHeader子类型实例的工厂方法。MessageHeader类型也定义了几个通过XmlWriter或者XmlDictionaryWriter序列化MessageHeader内容的方法。

创建一个MessageHeader对象

MessageHeader类型上定义的几个CreateHeader工厂方法。每个工厂方法接受一组参数,但是表示消息头块的name (String)、namespace (String)和 value (Object)的三个参数一直存在。其它参数允许我们自定义序列化器,还有其它SOAP消息头块的属性如mustUnderstand、actor和relay。下面代码演示了如何创建一个简单的带有WS-Addressing 规范里定义的MessageID 的MessageHeader对象:

String WSAddNS = "http://www.w3.org/2005/08/addressing";
MessageHeader header = MessageHeader.CreateHeader("MessageID",
WSAddNS, new UniqueId().ToString());
Console.WriteLine(header.ToString());
The following output is generated when this code executes:
运行结果如下:

<MessageID xmlns="http://www.w3.org/2005/08/addressing">
urn:uuid:
</MessageID>
注意到为了创建序列化为WS-Addressing MessageID (本例中使用String)消息头块的MessageHeader 对象,必须知道XML namespace和MessageID的name。你,我是不知道,但是我是不愿意记住WS-*规范里的一对消息块name.WCF架构师也有相同的感受,他们已经为我们提供了创建符合WS-*规范的消息头块的方式。本书里我们会看到这几种方式,本章里的“MessageHeaders类型”一节就会讲到这些内容。
这里要着重指出的是外貌可以构建表示自定义的消息头块的MessageHeader对象,它不需要遵循WS-*规范。例如,在消息发送到另一个消息参与者之前,订单处理系统或许要增加一个名为PurchaseOrderInfo的消息头块到Message上。因此,从前面的例子看出,我们可以简单地改变XML namespace、消息头块的name和消息头块的值来满足程序的需求。

MessageHeader header = MessageHeader.CreateHeader("PurchaseOrderDate",
"http://wintellect.com/POInfo", DateTime.Now);
Console.WriteLine(header.ToString());
This code generates the following output:
代码输出结果如下:

<PurchaseOrderDate xmlns="http://wintellect.com/POInfo">
2007-01-12T09:18:52.020824-04:00
</PurchaseOrderDate>
备注:正如你将在第九章里看到的,WCF基础结构可以使用消息契约为我们做这些工作。当我们使用这种简单、不易出错的方法时,WCF基础结构会执行类似前面的代码。同样要重点指出的是MessageHeader本身没什么价值,想赋予其任何意义,我们需要在Message对象里引用这个对象。你会在本章后面的“MessageHeaders类型“一节里详细学习这些内容。
 
 

序列化一个MessageHeader对象
MessageHeader类型一致了几个序列化和编码MessageHeader对象状态的成员。像Message类型一样,大部分成员名字以Write开头,并且接受一个XmlWrite或者一个XmlDictionaryWriter作为参数。MessageHeader类型同样定义了protected的抽象方法OnWriteHeaderContents和protected 的虚方法OnWriteStartHeader,它们允许MessageHeader的子类可以对MessageHeader的序列化做更多控制。MessageHeader类型里的Write方法会调用适当的protected方法,因此把序列化的任务交给了继承子类型。
备注:对于我来说,很难想象一个理由,在Message序列化的更大的上下文环境外去序列化一个MessageHeader对象。换句话说,唯一一次你需要考虑MessageHeader序列化,就是当你序列化一个Message的时候。因为Message类型的上定义的Write方法只能序列化SOAP envelope 和SOAP body。因此序列化Message的时候,序列化MessageHeader对象是必须的。我们会在本章的后面一节“MessageHeaders 类型“讨论这个主题。
 
下面代码演示了如何调用Write方法通过XmlDictionaryWriter去序列化一个MessageHeader对象:

[Serializable]
sealed class PurchaseOrderInfo {
internal Int32 PONumber;
internal DateTime? IssueDate;
internal Double? Amount;
    
internal PurchaseOrderInfo(Int32 ponumber,
                                                         DateTime? issueDate,
                                                         Double? amount){
        PONumber = ponumber;
        IssueDate = issueDate;
        Amount = amount;
}
}
class Program {
static void Main(){
        // create an object to store in the MessageHeader
        PurchaseOrderInfo poinfo = new PurchaseOrderInfo(1000,
                                                                                                         DateTime.Now,
                                                                                                         10.92);
        // create the MessageHeader
        MessageHeader header = MessageHeader.CreateHeader(
            "PurchaseOrderInfo", "http://wintellect.com/POInfo", poinfo);
    
        MemoryStream stream = new MemoryStream();
        XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(
            stream, Encoding.UTF8, false);
        // Serialize the MessageHeader via an XmlDictionaryWriter
        header.WriteHeader(writer, MessageVersion.Soap12WSAddressing10);
        writer.Flush();
        stream.Position = 0;
        // Show the contents of the Stream
        Console.WriteLine(new StreamReader(stream).ReadToEnd());
}
}
执行代码,输出如下结果:

<PurchaseOrderInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://wintellect.com/POInfo">
<Amount xmlns="http://schemas.datacontract.org/2004/07/">
        10.92
</Amount>
<IssueDate xmlns="http://schemas.datacontract.org/2004/07/">
        2007-01-11T15:06:25.515625-04:00
</IssueDate>
<PONumber xmlns="http://schemas.datacontract.org/2004/07/">
        1000
</PONumber>
</PurchaseOrderInfo>
注意到结果里包含PurchaseOrderInfo的子节点信息。驻留的这些信息是序列化PurchaseOrderInfo对象的副产品,而不是MessageHeader对象的直接作用结果。为什么,你也许会问,我们要在乎这些序列化的的消息头里的驻留信息吗?我们在乎因为许多消息头块都定义在WS-*规范里,并且很多自定义消息头的结构和驻留消息条目一样。概括地说,当序列化MessageHeader时,如果我们需要创建驻留消息条目,我们必须传递一个对象给MessageHeader的序列化工厂方法,或者MessageHeader的子类型,控制序列化工作。MessageHeader类型的子类型比WCF默认的序列化器提供了更多控制。当然比开发我们自己的序列化器更加简单。因此,WCF API内部使用MessageHeader的子类型作为序列化WS-*消息头块的一种方法。

WS-Addressing终结点规范

正如你在第二章里看到的,WS-Addressing为SOAP消息提供了一种统一的、标准化的寻址方式,这个标准的核心之一就是终结点规范。如其在WS-Addressing定义的一样,终结点都有与下面类似通用结构:

<wsa:EndpointReference xmlns:wsa="..." xmlns:wnt="...">
<wsa:Address>http://wintellect.com/OrderStuff</wsa:Address>
<wsa:ReferenceParameters>
        <wnt:OrderID>9876543</wnt:OrderID>
        <wnt:ShoppingCart>123456</wnt:ShoppingCart>
</wsa:ReferenceParameters>
</wsa:EndpointReference>
这些信息是SOAP消息里的一个消息头块。记住MessageHeader类型是对SOAP消息头块的CLR抽象,我们可以假定MessageHeader对象是对endpoint参考规范的CLR抽象。从前面的结构里注意到,这些item隶属于EndpointReference节点,并且和上一节提到的一样,它们带来了一些有趣的序列化挑战。
如果我们要构建一个可以序列化符合全部规范的MessageHeader对象(address和参数信息条目),我们有三种选择:
·         定义一个表示终结点的引用,并且传递给它一个CreateHeader工厂方法的实例作为Object参数。
·         MessageHeader的子类型使用自定义序列化方式。
·         定义一个表示终结点规范的类型和MessageHeader的子类型。
在尝试了第一种方法以后,我们发现这个方式不可行(你会在第九章详细学习序列化内容),因此强制我们考虑后面的2种方法。我们可以使用第二种方法,但是如果我们重构我们的设计,我们会很快发现我们的程序需要一个表示终结点的类型。话句话说,我们提出了一个需要自己定义终结点参考的情形。由于这些事实,WCF团队使用了第三种方法。他们定义了EndpointAddress来表示一个WS-Addressing终结点参考规范并且MessageHeader的子类型。通过这种组合我们可以通过MessageHeader对象表示一个终结点参考规范并且完全序列化它。你会在下一节MessageHeaders学习到详细内容。
 

MessageHeader外传

MessageHeader类型的其它几个方面也值得提一下。最显著的一点就是在实例化以后没有直接获取MessageHeader值的方法。咋一看,这还真是个问题,特别是当我们查询反序列化后的SOAP消息头块的内容时。Message 类型的Headers属性提供了解决这个困难的办法,并且这个类型定义了提取MessageHeader内容的机制。我们会在下一节的“MessageHeaders 类型“里详细介绍。
另外一个MessageHeader类型的奇怪成员就是就是IsReferenceParameter只读属性。在我们查询SOAP消息头块的内容时会非常有用,这个属性表示MessageHeader对象是否是一个WS-Addressing引用参数或者引用属性。你或许会对自己说,“你刚才不是说一个引用参数/属性在作用上,MessageHeader 对象表示一个终结点引用?“。是的,我这样说过,但是这还不能解决问题,就是判断一个MessageHeader对象是一个引用参数或者是引用属性.
考虑SOAP 消息里的To项目的结构,如下所示:

<S:Envelope xmlns:S="..." xmlns:wsa="..." xmlns:wnt="... ">
<S:Header>
        ...
        <wsa:To>http://wintellect.com/OrderStuff</wsa:To>
        <wnt:OrderID wsa:IsReferenceParameter="true">9876543</wnt:OrderID>
        <wnt:ShoppingCart wsa:IsReferenceParameter="true">
            123456
        </wnt:ShoppingCart>
         ...
</S:Header>
<S:Body>
和演示的一样,OrderID 和ShoppingCart是独立的消息头块,并且表示符合终结点规范的参数的引用。与To URI一起使用,他们可以用来创建一个终结点引用,因此他们和别的消息头块不同。我们可以容易地构建表示To终结点逻辑引用的OrderID和ShoppingCart条目的MessageHeader对象,但是区分这些MessageHeader对象可没那么容易,除非有IsReferenceParameter属性。换句话说,当反序列化SOAP消息为Message对象,并且查询MessageHeader对象的时候,我们能够通过检查IsReferenceParameter属性确定哪些对象是引用参数。一旦确定了那些是引用参数,我们可以将其与To URI一起使用,因此可以高效地创建一个终结点引用。你会在下一节里学习关于这个问题的更多内容。

 本文转自 frankxulei 51CTO博客,原文链接:http://blog.51cto.com/frankxulei/318582,如需转载请自行联系原作者



网友评论

登录后评论
0/500
评论
技术小胖子
+ 关注