當客戶端調用一個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接口發送更多信息時,但是又不能改變接口增加參數而影響其它調用者,通過消息報頭的傳遞方式可以達到在最小的影響范圍內實現業務需求。
客戶端:

服務端:

