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