上一篇:WCF技术剖析之十七:消息(Message)详解(中篇)

在《消息(Message)详解》系列的上篇中篇,先后对消息版本、详细创建、状态机和基于消息的基本操作(读取、写入、拷贝、关闭)进行了深入剖析,接下来我们来谈谈消息的另一个重要组成部分:消息报头(Message Header)。

按照SOAP1.1或者SOAP1.2规范,一个SOAP消息由若干SOAP报头和一个SOAP主体构成,SOAP主体是SOAP消息的有效负载,一个SOAP消息必须包含一个唯一的消息主体。SOAP报头是可选的,一个SOAP消息可以包含一个或者多个SOAP报头,SOAP报头一般用于承载一些控制信息。消息一经创建,其主体内容不能改变,而SOAP报头则可以自由地添加、修改和删除。正是因为SOAP的这种具有高度可扩展的设计,使得SOAP成为实现SOA的首选(有这么一种说法SOAP= SOA Protocol)。

按照SOAP 1.2规范,一个SOAP报头集合由一系列XML元素组成,每一个报头元素的名称为Header,命名空间为http://www.w3.org/2003/05/soap-envelope。每一个报头元素可以包含任意的属性(Attribute)和子元素。在WCF中,定义了一系列类型用于表示SOAP报头。

一、MessageHeaders、MessageHeaderInfo、MessageHeader和MessageHeader<T>

在Message类中,消息报头集合通过只读属性Headers表示,类型为System.ServiceModel.Channels.MessageHeaders。MessageHeaders本质上就是一个System.ServiceModel.Channels.MessageHeaderInfo集合。
  1.   1: public abstract class Message : IDisposable<!--CRLF-->
  2.   2: {  <!--CRLF-->
  3.   3:    //其他成员<!--CRLF-->
  4.   4:    public abstract MessageHeaders Headers { get; }<!--CRLF-->
  5.   5: }
复制代码
  1.   1: public sealed class MessageHeaders : IEnumerable<MessageHeaderInfo>, IEnumerable<!--CRLF-->
  2.   2: {<!--CRLF-->
  3.   3:    //省略成员<!--CRLF-->
  4.   4: }
复制代码
MessageHeaderInfo是一个抽象类型,是所有消息报头的基类,定义了一系列消息SOAP报头的基本属性。其中Name和Namespace分别表示报头的名称和命名空间,Actor、MustUnderstand、Reply与SOAP 1.1或者SOAP 1.2规定SOAP报头同名属性对应。需要对SOAP规范进行深入了解的读者可以从W3C官方网站下载相关文档。
  1.   1: public abstract class MessageHeaderInfo
  2.   2: {
  3.   3:    protected MessageHeaderInfo();
  4.   4:   
  5.   5:    public abstract string Actor { get; }
  6.   6:    public abstract bool    IsReferenceParameter { get; }
  7.   7:    public abstract bool    MustUnderstand { get; }
  8.   8:    public abstract string Name { get; }
  9.   9:    public abstract string Namespace { get; }
  10.   10:    public abstract bool    Relay { get; }
  11.   11: }
复制代码
当我们针对消息报头编程的时候,使用到的是另一个继承自MessageHeaderInfo的抽象类:System.ServiceModel.Channels.MessageHeader。除了实现MessageHeaderInfo定义的抽象只读属性外,MessageHeader中定义了一系列工厂方法(CreateHeader)方便开发人员创建MessageHeader对象。这些CreateHeader方法接受一个可序列化的对象,并以此作为消息报头的内容,WCF内部会负责从对象到XML InfoSet的序列化工作。此外,可以通过相应的WriteHeader方法对MessageHeader对象执行写操作。MessageHeader定义如下:
  1.   1: public abstract class MessageHeader : MessageHeaderInfo
  2.   2: {
  3.   3:    public static MessageHeader CreateHeader(string name, string ns, object value);
  4.   4:    public static MessageHeader CreateHeader(string name, string ns, object value, bool mustUnderstand);
  5.   5:    //其他CreateHeader方法
  6.   6:   
  7.   7:    public void WriteHeader(XmlDictionaryWriter writer, MessageVersion messageVersion);
  8.   8:    public void WriteHeader(XmlWriter writer, MessageVersion messageVersion);
  9.   9:    //其他WriteHeader方法
  10.   10:
  11.   11:    public override string Actor { get; }
  12.   12:    public override bool IsReferenceParameter { get; }
  13.   13:    public override bool MustUnderstand { get; }
  14.   14:    public override bool Relay { get; }
  15.   15: }
复制代码
除了MessageHeader,WCF还提供一个非常有价值的泛型类:System.ServiceModel. MessageHeader<T>,泛型参数T表示报头内容对应的类型,MessageHeader<T>为我们提供了强类型的报头创建方式。由于Message的Headers属性是一个MessageHeaderInfo的集合,MessageHeader<T>并不能直接作为Message对象的消息报头。GetUntypedHeader方法提供了从MessageHeader<T>对象到MessageHeader对象的转换。MessageHeader<T>定义如下:
  1.   1: public class MessageHeader<T>
  2.   2: {
  3.   3:    public MessageHeader();
  4.   4:    public MessageHeader(T content);
  5.   5:    public MessageHeader(T content, bool mustUnderstand, string actor, bool relay);
  6.   6:    public MessageHeader GetUntypedHeader(string name, string ns);
  7.   7:
  8.   8:    public string Actor { get; set; }
  9.   9:    public T Content { get; set; }
  10.   10:    public bool MustUnderstand { get; set; }
  11.   11:    public bool Relay { get; set; }
  12.   12: }
复制代码
接下来,我们通过一个简单的例子演示如何为一个Message对象添加报头。假设在一个WCF应用中,我们需要在客户端和服务端之间传递一些上下文(Context)的信息,比如当前用户的相关信息。为此我定义一个ApplicationContext类,这是一个集合数据契约(关于集合数据契约,可以参考我的文章:WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇))。ApplicationContext是一个字典,为了简单起见,key和value均使用字符串。ApplicationContext不能被创建(构造函数被私有化),只能通过静态只读属性Current得到。当前ApplicationContext存入CallContext从而实现了在线程范围内共享的目的。在ApplicationContext中定义了两个属性UserName和Department,表示用户名称和所在部门。3个常量分别表示ApplicationContext存储于CallContext的Key,以及置于MessageHeader后对应的名称和命名空间。
  1.   1: [CollectionDataContract(Namespace = "http://www.artech.com/", ItemName = "Context", KeyName = "Key", ValueName = "Value")]
  2.   2: public class ApplicationContext : Dictionary<string, string>
  3.   3: {
  4.   4:    private const string callContextKey = "__applicationContext";
  5.   5:    public const string HeaderLocalName = "ApplicationContext";
  6.   6:    public const string HeaderNamespace = [url]http://www.artech.com/[/url];
  7.   7:
  8.   8:    private ApplicationContext()
  9.   9:    { }
  10.   10: 
  11.   11:    public static ApplicationContext Current
  12.   12:    {
  13.   13:        get
  14.   14:        {
  15.   15:            if (CallContext.GetData(callContextKey) == null)
  16.   16:            {
  17.   17:                CallContext.SetData(callContextKey, new ApplicationContext());
  18.   18:            }
  19.   19:            return (ApplicationContext)CallContext.GetData(callContextKey);
  20.   20:        }
  21.   21:    }
  22.   22:
  23.   23:    public string UserName
  24.   24:    {
  25.   25:        get
  26.   26:        {
  27.   27:            if (!this.ContainsKey("__username"))
  28.   28:            {
  29.   29:                return string.Empty;
  30.   30:            }
  31.   31:            return this["__username"];
  32.   32:        }
  33.   33:        set
  34.   34:        {
  35.   35:            this["__username"] = value;
  36.   36:        }
  37.   37:    }
  38.   38: 
  39.   39:    public string Department
  40.   40:    {
  41.   41:        get
  42.   42:        {
  43.   43:            if (!this.ContainsKey("__department"))
  44.   44:            {
  45.   45:                return string.Empty;
  46.   46:            }
  47.   47:            return this["__department"];
  48.   48:        }
  49.   49:        set
  50.   50:        {
  51.   51:            this["__department"] = value;
  52.   52:        }
  53.   53:    }
  54.   54: }
复制代码
在下面代码中,首先对当前ApplicationContext进行相应的设置,然后创建MessageHeader<ApplicationContext>对象。通过调用GetUntypedHeader转换成MessageHeader对象之后,将其添加到Message的Headers属性集合中。后面是生成的SOAP消息。
  1.   1: Message message = Message.CreateMessage(MessageVersion.Default, [url]http://www.artech.com/myaction[/url]);
  2.   2: ApplicationContext.Current.UserName = "Foo";
  3.   3: ApplicationContext.Current.Department = "IT";
  4.   4: MessageHeader<ApplicationContext> header = new MessageHeader<ApplicationContext>(ApplicationContext.Current);
  5.   5: message.Headers.Add(header.GetUntypedHeader(ApplicationContext.HeaderLocalName, ApplicationContext.HeaderNamespace));
  6.   6: WriteMessage(message, @"e:\message.xml");
复制代码
  1. 1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  2.   2:    <s:Header>
  3.   3:        <a:Action s:mustUnderstand="1">http://www.artech.com/myaction<;/a:Action>
  4.   4:        <ApplicationContext xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.artech.com/">
  5.   5:            <Context>
  6.   6:                <Key>__username</Key>
  7.   7:                <Value>Foo</Value>
  8.   8:            </Context>
  9.   9:            <Context>
  10.   10:                <Key>__department</Key>
  11.   11:                <Value>IT</Value>
  12.   12:            </Context>
  13.   13:        </ApplicationContext>
  14.   14:    </s:Header>
  15.   15:    <s:Body />
  16.   16: </s:Envelope>
复制代码
TOP