天天看點

WCF技術剖析之十七:消息(Message)詳解(下篇)

按照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集合。

MessageHeaderInfo是一個抽象類型,是所有消息報頭的基類,定義了一系列消息SOAP報頭的基本屬性。其中Name和Namespace分别表示報頭的名稱和命名空間,Actor、MustUnderstand、Reply與SOAP 1.1或者SOAP 1.2規定SOAP報頭同名屬性對應。需要對SOAP規範進行深入了解的讀者可以從W3C官方網站下載下傳相關文檔。

當我們針對消息報頭程式設計的時候,使用到的是另一個繼承自MessageHeaderInfo的抽象類:System.ServiceModel.Channels.MessageHeader。除了實作MessageHeaderInfo定義的抽象隻讀屬性外,MessageHeader中定義了一系列工廠方法(CreateHeader)友善開發人員建立MessageHeader對象。這些CreateHeader方法接受一個可序列化的對象,并以此作為消息報頭的内容,WCF内部會負責從對象到XML InfoSet的序列化工作。此外,可以通過相應的WriteHeader方法對MessageHeader對象執行寫操作。MessageHeader定義如下:

除了MessageHeader,WCF還提供一個非常有價值的泛型類:System.ServiceModel. MessageHeader<T>,泛型參數T表示報頭内容對應的類型,MessageHeader<T>為我們提供了強類型的報頭建立方式。由于Message的Headers屬性是一個MessageHeaderInfo的集合,MessageHeader<T>并不能直接作為Message對象的消息報頭。GetUntypedHeader方法提供了從MessageHeader<T>對象到MessageHeader對象的轉換。MessageHeader<T>定義如下:

在下面代碼中,首先對目前ApplicationContext進行相應的設定,然後建立MessageHeader<ApplicationContext>對象。通過調用GetUntypedHeader轉換成MessageHeader對象之後,将其添加到Message的Headers屬性集合中。後面是生成的SOAP消息。

二、執行個體示範:通過消息報頭傳遞上下文資訊

在示範添加消息報頭的例子中,建立了一個ApplicationContext,這個類型将繼續為本案例服務。上面僅僅是示範如果為一個現成的Message對象添加相應的報頭,在本例中,我們将示範在一個具體的WCF應用中如何通過添加消息報頭的方式從用戶端向服務端傳遞一些上下文資訊。

上面我們定義的ApplicationContext借助于CallContext實作了同一線程内資料的上下文消息的共享。由于CallContext的實作方式是将資料存儲于目前線程的TLS(Thread Local Storage)中,是以它僅僅在用戶端或者服務端執行的線程中有效。現在我們希望相同的上下文資訊能夠在用戶端和服務端之間傳遞,毫無疑問,我們隻有唯一的辦法:就是将資訊存放在請求消息和回複消息中。圖1大體上示範了具體的實作機制。

用戶端的每次服務調用,會将目前ApplicationContext封裝成MessageHeader,存放到出棧消息(Outbound Message)的SOAP報頭中;服務端在接收到入棧消息(InBound message)後,将其取出,作為服務端的目前ApplicationContext。由此實作了用戶端向服務端的上下文傳遞。從服務端向用戶端上下文傳遞的實作與此類似:服務端将目前ApplicationContext植入出棧消息(Outbound Message)的SOAP報頭中,接收到該消息的用戶端将其取出,覆寫掉現有上下文的值。

WCF技術剖析之十七:消息(Message)詳解(下篇)

圖1 上下文資訊傳遞在消息交換中的實作

我們知道了如何實作消息報頭的建立,現在需要解決的是如何将建立的消息報頭植入到出棧和入棧消息報頭集合中。我們可以借助System.ServiceModel.OperationContext實作這樣的功能。OperationContext代表目前操作執行的上下文,定義了一系列與目前操作執行有關的上下文屬性,其中就包含出棧和入棧消息報頭集合。對于一個請求-回複模式服務調用來講,IncomingMessageHeaders和OutgoingMessageHeaders對于用戶端分别代表回複和請求消息的SOAP報頭,對于服務端則與此相反。

注: OperationContext代表服務操作執行的上下文。通過OperationContext可以得到出棧和入棧消息的SOAP報頭清單、消息屬性或者HTTP報頭。對于Duplex服務,在服務端可以通過OperationContext得到回調對象。此外通過OperationContext還可以得到基于目前執行的安全方面的屬性一起的其他相關資訊。

有了上面這些鋪墊,對于我們即将示範的案例就很好了解了。我們照例建立一個簡單的電腦的例子,同樣按照我們經典的4層結構,如圖2所示。

WCF技術剖析之十七:消息(Message)詳解(下篇)

圖2 上下文傳遞案例解決方案結構

先看看服務契約(ICalculator)和服務實作(CalculatorService)。在Add操作的具體實作中,先通過OperationContext.Current.IncomingMessageHeaders,根據預先定義在ApplicationContext中的報頭名稱和命名空間得到從用戶端傳入的ApplicationContext,并将其輸出。待運算結束後,修改服務端目前ApplicationContext的值,并将其封裝成MessageHeader,通過OperationContext.Current.OutgoingMessageHeaders植入到回複消息的SOAP報頭中。

用戶端的代碼與服務端在消息報頭的設定和擷取正好相反。在服務調用代碼中,先初始化目前ApplicationContext,通過ChannelFactory<ICalculator>建立服務代理對象。根據建立的服務代理對象建立OperationContextScope對象。在該OperationContextScope對象的作用範圍内(using塊中),将目前的ApplicationContext封裝成MessageHeader并植入出棧消息的報頭清單中,待正确傳回執行結果後,擷取服務端植入回複消息中傳回的AppicationContext,并覆寫掉現有的Context相應的值。

注: 同Transaction和TransactionScope一樣,OperationContextScope定義了目前OperationContext存活的範圍。對于用戶端來說,目前的OperationContext生命周期和OperationContextScope一樣,一旦成功建立OperationContextScope,就會建立目前的OperationContext,當OperationContextScope的Dispose方法被執行,目前的OperationContext對象也相應被回收。

下面的兩段文字分别代表服務端(Hosting)和用戶端的輸出結果,從中可以很清晰地看出,AppContext實作了在用戶端和服端之間的雙向傳遞。

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。