当客户端调用一个WCF接口时,客户端将请求消息发送到服务端,服务端再返回回复消息。WCF内部实现了消息处理的所有细节,但是并不意味着一切不可更改。WCF也提供了一些方法让开发人员在消息发送前对消息进行修改,在收到消息后也可以获取消息主体、报头。本篇介绍一个例子来手工控制消息发送的细节。介绍之前先介绍几个类。
1.创建消息报头MessageHeader
MessageHeader没有提供public构造函数,可以通过
(1)静态方法public static MessageHeader CreateHeader(string name, string ns, object value)创造一个MessageHeader对象。value是一个可序列化的对象,默认序列化器是DataContractSerializer。
(2) MessageHeader<T>提供了一个泛型构造消息报头的方式。创建一个MessageHeader<T>对象后,通过调用GetUntypedHeader并指定名称和命名空间可以更方便的构造一个报头。
MessageHeader<myHeaderElement> header = new MessageHeader<myHeaderElement>(element); MessageHeader head= header.GetUntypedHeader("myHeaderElement", "http://cnblogs.com/lh218")
更多用法可以参考MSDN MessageHeader<T>类 、MessageHeader类
2.获取当前即将发送的消息、收到的消息
通过服务当前上下文可以获得即将发送的消息报头集合、消息属性集合,收到的消息报头集合、消息属性集合。可以在这些集合中添加自定义消息报头,就可以人为的定制消息内容。值得一提的是消息属性只能够本地信道栈使用,客户端的消息属性无法传送到服务端。
OperationContext.Current.IncomingMessageHeaders:获取收到的消息报头
OperationContext.Current.IncomingMessageProperties:获取收到的消息属性
OperationContext.Current.OutgoingMessageHeaders:即将要发送的消息的消息报头
OperationContext.Current.OutgoingMessageProperties:即将要发送的消息的消息属性
更多信息参考OperationContext 属性
先定义一个自定义类,后续添加到消息报头中。
[DataContract] public class myHeaderElement { [DataMember] public string myAction; }
下面是客户端代码。
(1)使用不加密的WS2007HttpBinding。
(2)向OutgoingMessageHeaders添加一个myHeaderElement报头,并指定名称和命名空间。
(3)向OutgoingMessageProperties中添加一个myHeaderElement属性。
(4)调用服务端的Add方法后立刻通过IncomingMessageHeaders获取从服务端返回的消息的消息报头。
static void Main(string[] args) { WS2007HttpBinding bind = new WS2007HttpBinding(); bind.Security = new WSHttpSecurity(); bind.Security.Mode = SecurityMode.None; ICalculator client = ChannelFactory<ICalculator>.CreateChannel(bind, new EndpointAddress("http://localhost:7799")); using (OperationContextScope scope = new OperationContextScope(client as IContextChannel)) { myHeaderElement element = new myHeaderElement() { myAction = "Hello,I'm Client,1+2 = ?" }; //创建报头,并添加到消息报头中 MessageHeader<myHeaderElement> header = new MessageHeader<myHeaderElement>(element); OperationContext.Current.OutgoingMessageHeaders.Add(header.GetUntypedHeader("myHeaderElement", "http://cnblogs.com/lh218")); //添加到属性 MessageProperties Properties = OperationContext.Current.OutgoingMessageProperties; Properties.Add("myHeaderElement", element); Console.WriteLine("属性数量:{0}",OperationContext.Current.OutgoingMessageProperties.Count); Console.WriteLine("属性中是否包含 “OrderProperty”:{0}", OperationContext.Current.OutgoingMessageProperties.ContainsKey("myHeaderElement")); //读取属性 object ele=null; OperationContext.Current.OutgoingMessageProperties.TryGetValue("myHeaderElement", out ele); Console.WriteLine("属性中的myAction:{0}", ((myHeaderElement)ele).myAction); client.Add(1, 2); myHeaderElement backElement = OperationContext.Current.IncomingMessageHeaders.GetHeader<myHeaderElement>("myHeaderElementBack", "http://cnblogs.com/lh218"); Console.WriteLine(backElement.myAction); } }
服务端代码:
(1)通过IncomingMessageProperties获取从客户端发送过来的属性。其实这里无法获取客户端设置的myHeaderElement属性,因为属性不是SOAP的标准内容,只在客户端内部信道栈使用。
(2)IncomingMessageHeaders获取报头内容并打印。
(3)OutgoingMessageHeaders添加一个报头内容发送给客户端。
public class Calculator : ICalculator { public int Add(int x, int y) { if (OperationContext.Current.IncomingMessageProperties != null) { object o=null; OperationContext.Current.IncomingMessageProperties.TryGetValue("myHeaderElement", out o); if (null != o && null != o as myHeaderElement) { myHeaderElement element1 = o as myHeaderElement; Console.WriteLine("From Properties:{0}", element1.myAction); } } Console.WriteLine("Service:"); //从报头获取myHeaderElement myHeaderElement element2 = OperationContext.Current.IncomingMessageHeaders.GetHeader<myHeaderElement>("myHeaderElement", "http://cnblogs.com/lh218"); Console.WriteLine("From Headers:{0}", element2.myAction); //创建报头,并添加到消息报头中 element2.myAction = "From Service:1+2=3"; MessageHeader<myHeaderElement> header = new MessageHeader<myHeaderElement>(element2); OperationContext.Current.OutgoingMessageHeaders.Add(header.GetUntypedHeader("myHeaderElementBack", "http://cnblogs.com/lh218")); return x + y; } }
服务寄宿程序代码:
class Program { static void Main(string[] args) { ServiceHost host = new ServiceHost(typeof(Calculator)); WS2007HttpBinding bind = new WS2007HttpBinding(); bind.Security = new WSHttpSecurity(); bind.Security.Mode = SecurityMode.None; host.AddServiceEndpoint(typeof(ICalculator), bind, "http://localhost:7799"); host.Opened += delegate { Console.WriteLine("Service Start!"); }; host.Open(); while (true) ; } } [ServiceContract] public interface ICalculator { [OperationContract] int Add(int x, int y); }
通过TcpTrace工具拦截到的客户端发给服务端的消息:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"> <s:Header> <a:Action s:mustUnderstand="1">http://tempuri.org/ICalculator/Add</a:Action> <myHeaderElement xmlns="http://cnblogs.com/lh218" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <myAction xmlns="http://schemas.datacontract.org/2004/07/ClassLibrary1">Hello,I'm Client,1+2 = ?</myAction> </myHeaderElement> <a:MessageID>urn:uuid:7d3c93ad-acc1-42d7-b198-7db118392f46</a:MessageID> <a:ReplyTo> <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo> <a:To s:mustUnderstand="1">http://localhost:7788/</a:To> </s:Header> <s:Body> <Add xmlns="http://tempuri.org/"> <x>1</x> <y>2</y> </Add> </s:Body> </s:Envelope>
可以看到在Header添加了一个myHeaderElement类型的元素。
运行效果,客户端发送的消息中添加了一个myHeaderElement类型对象作为消息报头,内容为:Hello,I'm Client,1+2 = ?
服务端回复的消息中,也有一个名为myHeaderElementBack的报头。内容为:From Service:1+2=3.
意义就在于当你需要向一个WCF接口发送更多信息时,但是又不能改变接口增加参数而影响其它调用者,通过消息报头的传递方式可以达到在最小的影响范围内实现业务需求。
客户端:
服务端: