資料傳輸架構概觀
Windows Communication Foundation (WCF) 可以視為訊息基礎結構。 它可以接收訊息、加以處理,然後分派到使用者程式碼執行進一步動作,或是使用使用者程式碼提供的資料建構訊息,然後傳遞到目的地。 此主題將針對進階程式開發人員,描述處理訊息與所包含資料的架構。 如需如何傳送與接收資料的簡化型工作導向檢視,請參閱 Specifying Data Transfer in Service Contracts。
注意
此主題討論無法藉由檢查 WCF 物件模型能夠得知的 WCF 實作詳細資料。 關於已記載的實作詳細資料,依序有兩件需要注意的事項。 首先,描述已經過簡化,實際的實作可能會因為最佳化或其他原因而更為複雜。 第二,絕不要依賴特定的實作詳細資料 (即使已記載於文件中),因為這些詳細資料可能會在不同的版本中 (甚至是在服務版本中) 變更而不另行通知。
基本架構
WCF 訊息處理功能的核心是 Message 類別,在使用 Message 類別中會詳細描述此類別。 WCF 的執行階段元件可以分為兩個主要部分:通道堆疊與服務架構,其中使用 Message 類別當做連接點。
通道堆疊負責在有效 Message 執行個體,以及對應至傳送或接收訊息資料的某些動作之間進行轉換。 在傳送端,通道堆疊會取用有效的 Message 執行個體,然後在經過某些處理後,執行邏輯上對應至傳送訊息的某些動作。 這些動作可能是傳送 TCP 或 HTTP 封包、將訊息存放在訊息佇列中、將訊息寫入資料庫、將訊息儲存在檔案共用,或是任何視實作而定的其他動作。 最常見的動作是透過網路通訊協定傳送訊息。 在接收端,則發生相反的情況—偵測到動作 (可能是 TCP 或 HTTP 封包抵達,或是任何其他動作),然後在經過處理後,通道堆疊會將這個動作轉換為有效的 Message 執行個體。
您可以藉由直接使用 Message 類別與通道堆疊來使用 WCF。 但是,這麼做是很困難並且耗時的。 此外,Message 物件不支援中繼資料,因此如果以這種方式使用 WCF,將無法產生強型別 WCF 用戶端。
因此,WCF 會包含提供易於使用之程式設計模型的服務架構,讓您用來建構與接收 Message
物件。 服務架構會透過服務合約的概念,將服務對應至 .NET Framework 型別,然後將訊息分派至使用者作業,這個作業是使用 OperationContractAttribute 屬性標記的單純 .NET Framework 方法 (如需詳細資料,請參閱設計服務合約 (部分機器翻譯))。 這些方法會有參數與傳回值。 在服務端,服務架構會將傳入的 Message 執行個體轉換成參數,然後將傳回值轉換成傳出的 Message 執行個體。 在用戶端,則執行相反的動作。 例如,請考量下列 FindAirfare
作業。
[ServiceContract]
public interface IAirfareFinderService
{
[OperationContract]
int FindAirfare(string FromCity, string ToCity, out bool IsDirectFlight);
}
<ServiceContract()> _
Public Interface IAirfareFinderService
<OperationContract()> _
Function FindAirfare(ByVal FromCity As String, _
ByVal ToCity As String, ByRef IsDirectFlight As Boolean) As Integer
End Interface
假設在用戶端呼叫 FindAirfare
。 用戶端的服務架構會將 FromCity
和 ToCity
參數轉換成傳出的 Message 執行個體,並將其傳遞至要傳送的通道堆疊。
在服務端,當來自通道堆疊的 Message 執行個體抵達時,服務架構會從訊息擷取相關資料以填入 FromCity
和 ToCity
參數,然後呼叫服務端的 FindAirfare
方法。 當方法傳回時,服務架構會取用傳回的整數值以及 IsDirectFlight
輸出參數,然後建立包含這項資訊的 Message 物件執行個體。 然後將 Message
執行個體傳遞至要傳回用戶端的通道堆疊。
在用戶端,包含回應訊息的 Message 執行個體會出現在通道堆疊。 服務架構會擷取傳回值以及 IsDirectFlight
值,然後將這些傳回給用戶端的呼叫者。
訊息類別
Message 類別是要用來當做訊息的抽象表示法,但其設計與 SOAP 訊息有強烈關聯。 Message 包含三段主要的資訊:訊息本文、訊息標頭和訊息屬性。
訊息本文
訊息本文是用來表示訊息的實際資料承載。 訊息本文一定會當做 XML Infoset 表示。 這並不表示在 WCF 中建立或接收的所有訊息都必須使用 XML 格式。 而是留給通道堆疊決定如何解譯訊息本文。 可能會將本文當做 XML 發出、轉換成某些其他格式,或甚至整個省略。 當然,在使用 WCF 提供的大多數繫結時,訊息本文會表示為 SOAP 封套之本文區段中的 XML 內容。
重點是要了解 Message
類別不一定需要包含使用 XML 資料表示本文的緩衝區。 就邏輯上來說, Message
會包含 XML Infoset,但是可能會使用動態方式建構這個 Infoset,並且一定不會以實體方式存在記憶體中。
將資料放入訊息本文
在將資料放入訊息本文方面並沒有統一的機制。 Message 類別有一個抽象方法, OnWriteBodyContents(XmlDictionaryWriter),它會取用 XmlDictionaryWriter。 Message 類別的每個子類別會負責覆寫這個方法,然後寫入專屬的內容。 訊息本文邏輯上會包含 OnWriteBodyContent
產生的 XML Infoset。 例如,請考量下列 Message
子類別。
public class AirfareRequestMessage : Message
{
public string fromCity = "Tokyo";
public string toCity = "London";
//code omitted…
protected override void OnWriteBodyContents(XmlDictionaryWriter w)
{
w.WriteStartElement("airfareRequest");
w.WriteElementString("from", fromCity);
w.WriteElementString("to", toCity);
w.WriteEndElement();
}
public override MessageVersion Version
{
get { throw new NotImplementedException("The method is not implemented.") ; }
}
public override MessageProperties Properties
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override MessageHeaders Headers
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override bool IsEmpty
{
get
{
return base.IsEmpty;
}
}
public override bool IsFault
{
get
{
return base.IsFault;
}
}
}
Public Class AirfareRequestMessage
Inherits Message
Public fromCity As String = "Tokyo"
Public toCity As String = "London"
' Code omitted…
Protected Overrides Sub OnWriteBodyContents(ByVal w As XmlDictionaryWriter)
w.WriteStartElement("airfareRequest")
w.WriteElementString("from", fromCity)
w.WriteElementString("to", toCity)
w.WriteEndElement()
End Sub
Public Overrides ReadOnly Property Version() As MessageVersion
Get
Throw New NotImplementedException("The method is not implemented.")
End Get
End Property
Public Overrides ReadOnly Property Properties() As MessageProperties
Get
Throw New Exception("The method or operation is not implemented.")
End Get
End Property
Public Overrides ReadOnly Property Headers() As MessageHeaders
Get
Throw New Exception("The method or operation is not implemented.")
End Get
End Property
Public Overrides ReadOnly Property IsEmpty() As Boolean
Get
Return MyBase.IsEmpty
End Get
End Property
Public Overrides ReadOnly Property IsFault() As Boolean
Get
Return MyBase.IsFault
End Get
End Property
End Class
實際上 AirfareRequestMessage
執行個體只包含兩個字串 ("fromCity" 和 "toCity")。 但是,邏輯上訊息會包含下列 XML Infoset:
<airfareRequest>
<from>Tokyo</from>
<to>London</to>
</airfareRequest>
當然,通常不會使用這種方式建立訊息,因為您可以使用服務架構從作業合約參數建立像之前的訊息。 此外, Message 類別有靜態 CreateMessage
方法,您可以用來搭配內容的一般型別建立訊息:空白訊息、包含使用 DataContractSerializer序列化為 XML 之物件的訊息、包含 SOAP 錯誤的訊息,以及包含使用 XmlReader表示之 XML 的訊息等等。
從訊息本文取得資料
您可以使用兩種主要方法擷取儲存在訊息本文中的資料:
您可以藉由呼叫 WriteBodyContents(XmlDictionaryWriter) 方法然後傳入 XML 寫入器,一次取得整個訊息本文。 完整的訊息本文會寫至這個寫入器。 一次取得整個訊息本文也稱為「 寫入訊息」(Writing a Message)。 當傳送訊息時寫入主要是由通道堆疊完成,通道堆疊的某些部分通常能夠存取整個訊息本文、加以編碼然後傳送。
從訊息本文取得資訊的另一種方法,是呼叫 GetReaderAtBodyContents() 然後取得 XML 讀取器。 然後可以藉由呼叫讀取器上的方法,依需要以循序方式存取訊息本文。 以片段方式取得訊息本文也稱為「 讀取訊息」(Reading a Message)。 主要是服務架構會在接收訊息時使用讀取訊息。 例如,當 DataContractSerializer 正在使用時,服務架構會透過本文取得 XML 讀取器,並且將其傳遞給還原序列化引擎,然後就會開始逐一讀取訊息項目並建構對應的物件圖形。
訊息本文只能擷取一次。 就可以使用順向資料流。 例如,您可以寫入 OnWriteBodyContents(XmlDictionaryWriter) 覆寫,以便從 FileStream 讀取然後使用 XML Infoset 的方式傳回結果。 永遠不需要「倒轉」至檔案的開頭。
WriteBodyContents
和 GetReaderAtBodyContents
方法只會檢查之前從未擷取過訊息本文,然後分別呼叫 OnWriteBodyContents
或 OnGetReaderAtBodyContents
。
WCF 中的訊息使用方式
大部分的訊息都能夠分類為「 傳出 」(Outgoing) (由服務架構建立並透過通道堆疊傳送) 或「 傳入 」(Incoming) (來自通道堆疊並由服務架構解譯)。 此外,通道堆疊可以在經過緩衝處理或資料流模式中運作。 服務架構可能也會公開經過資料流處理或非資料流處理的程式設計模型。 這種做法會造成下表列出的案例,以及其實作的簡化詳細資料。
訊息類型 | 訊息中的本文資料 | 寫入 (OnWriteBodyContents) 實作 | 讀取 (OnGetReaderAtBodyContents) 實作 |
---|---|---|---|
傳出,從經過非資料流處理的程式設計模型建立 | 寫入訊息需要的資料 (例如,物件與需要序列化它的 DataContractSerializer 執行個體)* | 自訂邏輯以根據儲存資料寫出訊息 (例如,呼叫 WriteObject 上的 DataContractSerializer (如果這是使用中的序列化程式))* |
呼叫 OnWriteBodyContents 、緩衝處理結果、透過緩衝區傳回 XML 讀取器 |
傳出,從經過資料流處理的程式設計模型建立 | 使用要寫入之資料的 Stream * |
使用 IStreamProvider 機制從儲存的資料流寫出資料* | 呼叫 OnWriteBodyContents 、緩衝處理結果、透過緩衝區傳回 XML 讀取器 |
從資料流通道堆疊傳入 | Stream 物件,表示透過網路傳入並且搭配 XmlReader 的資料 |
使用 XmlReader 從儲存的 WriteNode 寫出內容 |
傳回儲存的 XmlReader |
從非資料流的通道堆疊傳入 | 包含搭配 XmlReader 之本文資料的緩衝區 |
使用 XmlReader 從儲存的 WriteNode 寫出內容 |
傳回儲存的 lang |
* 這些項目不會直接實作在 Message
子類別中,而是在 BodyWriter 類別的子類別中。 如需 BodyWriter的詳細資訊,請參閱 Using the Message Class。
訊息標頭
包含標頭的訊息。 標頭邏輯上是由與名稱、命名空間,以及一些其他屬性關聯的 XML Infoset 所組成。 使用 Headers
的 Message屬性可以存取訊息標頭。 每個標頭是由 MessageHeader 類別表示。 通常當使用設定的通道堆疊搭配 SOAP 訊息運作時,訊息標頭會對應到 SOAP 訊息標頭。
將資訊放入訊息標頭以及從中擷取資訊就像使用訊息本文。 因為不支援資料流所以處理序稍微經過簡化。 您可以存取相同標頭的內容一次以上,並且以任意順序存取標頭,以強制一定要緩衝處理標頭。 並沒有取得標頭之 XML 讀取器的一般用途機制,但是 WCF 內部有個 MessageHeader
子類別,表示具備這類功能的可讀取標頭。 當使用自訂應用程式標頭的訊息傳入時, MessageHeader
的型別是由通道堆疊建立。 這可讓服務架構使用還原序列化引擎 (例如 DataContractSerializer) 來解譯這些標頭。
如需詳細資訊,請參閱使用 Message 類別。
郵件內容
訊息會包含屬性。 「屬性」是與字串名稱關聯的任何 .NET Framework 物件。 透過 Properties
的 Message
屬性可以存取屬性。
不像訊息本文和訊息標頭 (通常會分別對應至 SOAP 本文與 SOAP 標頭),訊息屬性通常不會與訊息一起傳送或接收。 訊息屬性的存在主要是當做通訊機制,將有關訊息的資料在通道堆疊的各個通道之間傳遞,以及在通道堆疊與服務模型之間傳遞。
例如,包含當做 WCF 一部分的 HTTP 傳輸通道,當其將回應傳送至用戶端時,能夠產生各種 HTTP 狀態碼,例如「404 (找不到)」和「500 (內部伺服器錯誤)」。 在傳送回覆訊息之前,其會檢查 Message
的 Properties
是否包含稱為 "httpResponse",其中包含 HttpResponseMessageProperty 型別物件的屬性。 如果找到這類屬性,它會查看 StatusCode 屬性然後使用該狀態碼。 如果找不到,就會使用預設「200 (確定)」代碼。
如需詳細資訊,請參閱使用 Message 類別。
整個訊息
到目前為止,已經單獨討論過存取訊息各部分的方法。 但是, Message 類別也提供方法與整個訊息一起運作。 例如, WriteMessage
方法會將整個訊息寫出至 XML 寫入器。
為了要能夠這樣做,就必須在整個 Message
執行個體與 XML Infoset 之間定義對應。 事實上這類對應已經存在:WCF 會使用 SOAP 標準定義這個對應。 當寫出 Message
執行個體當做 XML Infoset 時,產生的 Infoset 是包含訊息的有效 SOAP 封套。 因此, WriteMessage
通常會執行下列步驟:
寫入 SOAP 封套項目開頭標記。
寫入 SOAP 標頭項目開頭標記、寫出所有的標頭,然後關閉標頭項目。
寫入 SOAP 本文項目開頭標記。
呼叫
WriteBodyContents
或對等的方法以寫出本文。關閉本文與封套項目。
之前的步驟與 SOAP 標準緊密相關。 因為事實上存在多個 SOAP 版本導致情況變得複雜;例如,可以在不了解使用中之 SOAP 版本的情況下正確寫出 SOAP 封套項目。 同樣地,在某些案例中,可能想要完全關閉這項複雜的 SOAP 特定對應。
針對這些用途,在 Version
會提供 Message
屬性。 當寫出訊息時可以設定要使用的 SOAP 版本,或是可以設定為 None
以避免任何 SOAP 特定對應。 如果 Version
屬性設定為 None
,與整個訊息一起運作的方法會當做訊息只是由其本文所組成;例如, WriteMessage
只會呼叫 WriteBodyContents
而不是執行上述列出的多個步驟。 預期在傳入訊息上,將會自動偵測 Version
並正確設定。
通道堆疊
通道
如同之前所述,通道堆疊負責將傳出 Message 執行個體轉換成某些動作 (例如在網路上傳送封包),或是將某些動作 (例如接收網路封包) 轉換成傳入 Message
執行個體。
通道堆疊是由一或多個依順序排列的通道組成。 傳出 Message
執行個體會傳遞至堆疊中的第一個通道 (也稱為「 最上層通道」(Topmost Channel)),然後再向下傳遞至堆疊中的下一個通道,以此類推。 訊息會終止在最後一個通道,稱為「 傳輸通道」(Transport Channel)。 傳入訊息來自傳輸通道,並且會在堆疊的通道間向上傳遞。 從最上層通道,訊息通常會傳遞至服務架構。 雖然這是應用程式訊息的常見模式,某些通道的運作方式卻有些不同;例如,通道可能會在沒有收到上層通道所傳遞訊息的情況下,傳送專屬的基礎結構訊息。
當訊息在堆疊內傳遞時,通道可能會在訊息上以各種方式作業。 最常見的作業是將標頭新增至傳出訊息,然後讀取傳入訊息上的標頭。 例如,通道可能會計算訊息的數位簽章,然後將其新增當做標頭。 通道也會在傳入訊息上檢查這個數位簽章標頭,然後封鎖沒有有效簽章的訊息避免在通道堆疊向上傳遞。 通道通常也會設定或檢查訊息屬性。 訊息本文通常不會修改 (雖然允許這麼做);例如,WCF 安全性通道能夠加密訊息本文。
傳輸通道與訊息編碼器
堆疊中的最底層通道負責將傳出 Message(其他通道已修改過) 實際轉換為某些動作。 在接收端,這是將某些動作轉換為讓其他通道處理之 Message
的通道。
如同之前所述,動作可能有很多種:經由各種通訊協定傳送或接收網路封包、在資料庫中讀取或寫入訊息,或是在訊息佇列中新增或清除佇列訊息,以提供為數不多的範例。 所有這些動作都有個共通點:需要在 WCFMessage
執行個體與能夠傳送、接收、寫入、新增佇列或清除佇列的實際位元組群組之間轉換。 將 Message
轉換為位元組群組的程序稱為「 編碼」(Encoding),而從位元組群組建立 Message
的反向程序稱為「 解碼」(Decoding)。
大部分的傳輸通道會使用稱為「 訊息編碼器 」(Message Encoder) 的元件以完成編碼與解碼工作。 訊息編碼器是 MessageEncoder 類別的子類別。 MessageEncoder
包含各種 ReadMessage
和 WriteMessage
方法多載,以便在 Message
和位元組群組之間轉換。
在傳送端,緩衝處理的傳輸通道會將從上一層通道接收到的 Message
物件傳遞到 WriteMessage
。 它會取回位元組陣列,然後用來執行其動作 (例如將這些位元組封裝成有效 TCP 封包,然後將它們傳送至正確的目的地)。 資料流傳輸通道會先建立 Stream
(例如,透過傳出 TCP 連線),然後同時傳遞需要傳送的 Stream
和 Message
至適當 WriteMessage
多載以寫出訊息。
在接收端,緩衝處理的傳輸通道會將傳入位元組 (例如,從傳入 TCP 封包) 擷取至陣列中,然後呼叫 ReadMessage
以取得能夠在通道堆疊進一步向上傳遞的 Message
物件。 資料流傳輸通道會建立 Stream
物件 (例如,經由傳入 TCP 連線的網路資料流),然後將其傳遞至 ReadMessage
以取回 Message
物件。
傳輸通道與訊息編碼器之間的分隔並不是強制性的;可以寫入不使用訊息編碼器的傳輸通道。 但是,這項分隔的優點是容易撰寫。 只要傳輸通道只使用基底 MessageEncoder,就能夠使用任何 WCF 或協力廠商訊息編碼器。 同樣地,相同的編碼器通常能夠在任何傳輸通道中使用。
訊息編碼器作業
若要描述編碼器的一般作業,考量下列四種案例會有幫助。
作業 | 註解 |
---|---|
編碼,經過緩衝處理 | 在經過緩衝處理模式中,編碼器通常會建立大小會變動的緩衝區,然後在緩衝區建立 XML 寫入器。 然後它會在要編碼的訊息上呼叫 WriteMessage(XmlWriter) 以寫出標頭,然後本文會使用 WriteBodyContents(XmlDictionaryWriter),如同本主題中說明有關 Message 的之前章節。 然後就會傳回緩衝區的內容 (以位元組陣列表示) 以便讓傳輸通道使用。 |
編碼,經過資料流處理 | 在經過資料流處理模式中,作業類似上述說明,但是更為簡單。 不需要緩衝區。 XML 寫入器會以一般方式在資料流上建立,然後在 WriteMessage(XmlWriter) 上呼叫 Message 以寫出至這個寫入器。 |
解碼,經過緩衝處理 | 當在經過緩衝處理模式中進行解碼時,通常會建立包含經過緩衝處理資料的特殊 Message 子類別。 會讀取訊息的標頭,然後建立位於訊息本文上的 XML 讀取器。 這是會跟 GetReaderAtBodyContents()一起傳回的讀取器。 |
解碼,經過資料流處理 | 當在經過資料流處理模式中進行解碼時,通常會建立特別訊息子類別。 資料流的進階程度剛好足以讀取所有標頭並位於訊息本文上。 然後會在資料流上建立 XML 讀取器。 這是會跟 GetReaderAtBodyContents()一起傳回的讀取器。 |
編碼器也能夠執行其他功能。 例如,編碼器能夠集區 XML 讀取器和寫入器。 有需要就建立新的 XML 讀取器或寫入器是很昂貴的。 因此,編碼器通常會使用可設定的大小,維護讀取器的集區以及寫入器的集區。 在先前所述的編碼器作業說明中,每當使用「建立 XML 讀取器/寫入器」的片語時,通常表示「從集區取用一個,或如果無法使用,請建立一個」。編碼器 (和解碼時所建立的 Message
子類別) 包含邏輯,在不再需要使用讀取器和寫入器時 (例如,當 Message
已關閉) 將其傳回至集區。
WCF 提供三種訊息編碼器 (雖然可以建立其他的自訂類型)。 提供的類型為文字、二進位以及訊息傳輸最佳化機制 (MTOM)。 在 Choosing a Message Encoder中會詳細描述這些類型。
IStreamProvider 介面
將含有經過資料流處理之本文的傳出訊息寫入至 XML 寫入器時, Message 將會在其 OnWriteBodyContents(XmlDictionaryWriter) 實作中使用類似下面一連串的呼叫:
在資料流前面寫入任何必要的資訊,(例如,開頭 XML 標記)。
寫入資料流。
在資料流後面寫入任何資訊,(例如,結束 XML 標記)。
這很適合用在類似文字 XML 編碼方式的編碼。 但是,有些編碼並不會將 XML Infoset 資訊 (例如,XML 項目的開頭及結束標記) 與包含在項目內的資料放在一起。 例如,在 MTOM 編碼中,訊息會分割為多個部分。 其中一個部分會包含 XML Infoset,而這其中可能會包含實際項目內容之其他部分的參考。 XML Infoset 通常會比資料流處理後的內容還要小,因此合理的做法是先緩衝處理 Infoset,再將它寫出,然後以資料流的處理方式寫入內容。 這表示在寫入結尾項目標記以前,應該還沒有寫出資料流。
為此,這時會使用 IStreamProvider 介面。 這個介面具有可以傳回要寫入之資料流的 GetStream() 方法。 寫出 OnWriteBodyContents(XmlDictionaryWriter) 中已由資料流處理之訊息本文的正確方式如下所示:
在資料流前面寫入任何必要的資訊,(例如,開頭 XML 標記)。
配合傳回要寫入之資料流的
WriteValue
實作,在接受 XmlDictionaryWriter 的 IStreamProvider上呼叫IStreamProvider
多載。在資料流後面寫入任何資訊,(例如,結束 XML 標記)。
透過這種方式,XML 寫入器就可以選擇呼叫 GetStream() 以及寫出經過資料流處理之資料的時機。 例如,文字及二進位 XML 寫入器將立即呼叫此方法,並在開始與結束標記之間寫出資料流處理內容。 MTOM 寫入器則可能會決定於稍後準備好要寫入訊息的適當部分時,再呼叫 GetStream() 。
在服務架構中表示資料
如同在此主題的「基本架構」章節中所述,服務架構是 WCF 的一部分,此外,也負責在訊息資料的方便使用之程式設計模型與實際 Message
執行個體之間進行轉換。 通常訊息交換在服務架構中會表示為使用 OperationContractAttribute 屬性標記的 .NET Framework 方法。 方法可以取用某些參數然後傳回值或輸出參數 (或兩者皆是)。 在服務端,輸入參數代表傳入訊息,而傳回值與輸出參數代表傳出訊息。 在用戶端,則是反向作業。 在 Specifying Data Transfer in Service Contracts中會詳細描述使用參數與傳回值之描述訊息的程式設計模型。 但是,這個章節將提供簡短的概觀。
程式設計模型
WCF 服務架構支援五種不同的描述訊息程式設計模型:
1.空訊息
這是最單純的案例。 若要描述空的傳入訊息,請不要使用任何輸入參數。
[OperationContract]
int GetCurrentTemperature();
<OperationContract()> Function GetCurrentTemperature() As Integer
若要描述空的傳出訊息,請使用 void 傳回值並且不要使用任何輸出參數:
[OperationContract]
void SetDesiredTemperature(int t);
<OperationContract()> Sub SetDesiredTemperature(ByVal t As Integer)
請注意這與單向作業合約不同:
[OperationContract(IsOneWay = true)]
void SetLightbulb(bool isOn);
<OperationContract(IsOneWay:=True)> Sub SetLightbulb(ByVal isOn As Boolean)
在 SetDesiredTemperature
範例中,會描述雙向訊息交換模式。 訊息會從作業傳回,但是訊息為空的。 可以從作業傳回錯誤。 在 "Set Lightbulb" 範例中,訊息交換模式是單向的,所以沒有需要描述的傳出訊息。 在此例中服務無法將任何狀態傳回用戶端。
2.直接使用訊息類別
可以在作業合約中直接使用 Message 類別 (或是其中一個子類別)。 在此例中,服務架構只會在不進行任何處理的情況下把作業的 Message
傳遞給通道堆疊 (反之亦然)。
直接使用 Message
有兩個主要的使用案例。 當沒有任何其他程式設計模型能提供足夠的彈性以描述訊息時,您可以在進階案例使用它。 例如,您可能想要使用磁碟上的檔案描述訊息,將檔案的屬性變成訊息標頭,而檔案的內容變成訊息本文。 然後可以建立類似下列的項目。
public class FileMessage : Message
// Code not shown.
Public Class FileMessage
Inherits Message
' Code not shown.
第二種在作業合約中常見的 Message
使用方式,是當服務不在意特定的訊息內容,並且將訊息當做黑箱進行作業時。 例如,您可能會有將訊息轉寄給多個其他收件者的服務。 合約寫入方式可以如下所示。
[OperationContract]
public FileMessage GetFile()
{
//code omitted…
FileMessage fm = new FileMessage("myFile.xml");
return fm;
}
<OperationContract()> Public Function GetFile() As FileMessage
'code omitted…
Dim fm As New FileMessage("myFile.xml")
Return fm
End Function
Action="*" 行會有效地關閉訊息分派,並且確定傳送至 IForwardingService
合約的所有訊息都能成功執行 ForwardMessage
作業 (通常發送器會檢查訊息的 "Action" 標頭以判定其想要執行的作業。Action="*" 表示「Action 標頭所有可能的值」)。Action="*" 與使用 Message 當做參數的組合也稱為「通用合約」,因為它能夠接收所有可能的訊息。 若要能夠傳送所有可能的訊息,請使用 Message 當做傳回值並且將 ReplyAction
設定為 "*"。 這會避免服務架構新增專屬的 Action 標頭,讓您能夠使用傳回的 Message
物件控制這個標頭。
3.訊息合約
WCF 提供宣告式程式設計模型以描述訊息,稱為「訊息合約」。 這個模型在 Using Message Contracts中有更詳細的說明。 整個訊息在本質上是由使用像是 MessageBodyMemberAttribute 和 MessageHeaderAttribute 之屬性的單一 .NET Framework 型別表示,以描述訊息合約類別與訊息之間的對應方式。
訊息合約提供對產生的 Message
執行個體提供許多控制 (雖然很明顯地不像直接使用 Message
類別一樣能夠提供那麼多的控制)。 例如,訊息本文通常是由多段資訊所組成,每段資訊以專屬的 XML 項目表示。 這些項目可能會直接發生在本文中 (「不包裝 」(Bare) 模式),或是可能「 包裝 」(Wrapped) 在內含 XML 項目中。 使用訊息合約程式設計模型能夠讓您決定要使用不包裝或包裝模式,然後控制包裝函式與命名空間名稱。
下列訊息合約的程式碼範例示範這些功能。
[MessageContract(IsWrapped = true, WrapperName = "Order")]
public class SubmitOrderMessage
{
[MessageHeader]
public string customerID;
[MessageBodyMember]
public string item;
[MessageBodyMember]
public int quantity;
}
<MessageContract(IsWrapped:=True, WrapperName:="Order")> _
Public Class SubmitOrderMessage
<MessageHeader()> Public customerID As String
<MessageBodyMember()> Public item As String
<MessageBodyMember()> Public quantity As Integer
End Class
標記為要序列化的項目 (使用 MessageBodyMemberAttribute、 MessageHeaderAttribute或其他相關屬性) 必須可序列化,才能參與訊息合約。 如需詳細資訊,請參閱這個主題稍後的<序列化>一節。
4.參數
想要描述作業而在多個資料片段執行作業的程式開發人員,通常不需要訊息合約提供的控制等級。 例如,當建立新的服務時,人們通常不想決定要使用不包裝或包裝模式,以及決定包裝函式項目的名稱。 做這些決定通常需要深入了解 Web 服務與 SOAP。
WCF 服務架構可以自動挑選最佳與最互通的 SOAP 表示法,以便在不需要強迫使用者做這些選擇的情況下,傳送或接收多個相關的資訊片段。 只要描述這些資訊片段當做作業合約的參數或傳回值,就可以完成這個動作。 例如,請考量下列作業合約。
[OperationContract]
void SubmitOrder(string customerID, string item, int quantity);
<OperationContract()> _
Sub SubmitOrder( _
ByVal customerID As String, _
ByVal item As String, _
ByVal quantity As Integer)
服務架構會自動決定將所有三項資訊片段 (customerID
、 item
和 quantity
) 放入訊息本文,然後將其包裝在名為 SubmitOrderRequest
的包裝函式項目中。
建議方法是將要傳送或接收的資訊描述當做作業合約參數的簡單清單,除非有特殊原因必須使用更複雜的訊息合約或 Message
架構的程式設計模型。
5.資料流
在作業合約中使用 Stream
或其中一個子類別,或是當做訊息合約中的單獨訊息本文部分,可以視為與上述不同的程式設計模型。 以這種方式使用 Stream
是確保合約能夠用在經過資料流處理方式的唯一辦法,除非寫入專屬的與資料流相容之 Message
子類別。 如需詳細資訊,請參閱大型資料和資料流。
當以這種方式使用 Stream
或其中一個子類別時,就不會叫用序列化程式。 針對傳出訊息會建立特殊的資料流 Message
子類別,然後會如同 IStreamProvider 介面上章節中所述寫出資料流。 針對傳入訊息,服務架構會在傳入訊息上建立 Stream
子類別,然後將其提供給作業使用。
程式設計模型限制
上述的程式設計模型不能任意組合。 例如,如果作業接受訊息合約類型,訊息合約必須是其唯一的輸入參數。 此外,然後作業必須傳回空訊息 (屬於 void 的傳回型別) 或另一個訊息合約。 在每個特定程式設計模型的主題中會描述這些程式設計模型限制: Using Message Contracts、 Using the Message Class和 Large Data and Streaming。
訊息格式器
藉由將名為「 訊息格式器 」(Message Formatter) 的元件插入服務架構可以支援上述程式設計模型。 訊息格式器是實作 IClientMessageFormatter 或 IDispatchMessageFormatter 介面 (或兩者都是) 的型別,以便分別在用戶端與服務 WCF 用戶端使用。
通常是藉由行為插入訊息格式器。 例如, DataContractSerializerOperationBehavior 會插入資料合約訊息格式器。 可以藉由在服務端的 Formatter 方法中將 ApplyDispatchBehavior(OperationDescription, DispatchOperation) 設定為正確的格式器,或是藉由在用戶端的 Formatter 方法中將 ApplyClientBehavior(OperationDescription, ClientOperation) 設定為正確的格式器以達到此目的。
下表列出訊息格式器可能會實作的方法。
介面 | 方法 | 動作 |
---|---|---|
IDispatchMessageFormatter | DeserializeRequest(Message, Object[]) | 將傳入 Message 轉換成作業參數 |
IDispatchMessageFormatter | SerializeReply(MessageVersion, Object[], Object) | 從作業傳回值/輸出參數建立傳出 Message |
IClientMessageFormatter | SerializeRequest(MessageVersion, Object[]) | 從作業參數建立傳出 Message |
IClientMessageFormatter | DeserializeReply(Message, Object[]) | 將傳入 Message 轉換為傳回值/輸出參數 |
序列化
當您使用訊息合約或參數描述訊息內容時,必須使用序列化在 .NET Framework 型別與 XML Infoset 表示法之間進行轉換。 序列化會用在 WCF 的其他地方;例如 Message 有個泛型 GetBody 方法,您可以用來讀取還原序列化為物件之訊息的整個本文。
WCF 支援兩種「不必經過任何處理就可以直接使用」的序列化技術,以序列化與還原序列化參數及訊息部分:DataContractSerializer 和 XmlSerializer
。 此外,您可以撰寫自訂序列化程式。 但是,WCF 的其他部分 (例如泛型 GetBody
方法或 SOAP 錯誤序列化) 可能會限制只能使用 XmlObjectSerializer 子類別 (DataContractSerializer 和 NetDataContractSerializer,而不是 XmlSerializer),或甚至被硬式編碼為只能使用 DataContractSerializer。
XmlSerializer
是在 ASP.NET Web 服務中使用的序列化引擎。 DataContractSerializer
是了解新資料合約程式設計模型的新序列化引擎。 DataContractSerializer
是預設選擇,而您可以使用 XmlSerializer
屬性,針對個別作業選擇使用 DataContractFormatAttribute 。
DataContractSerializerOperationBehavior 和 XmlSerializerOperationBehavior 是分別負責插入 DataContractSerializer
與 XmlSerializer
之訊息格式器的作業行為。 DataContractSerializerOperationBehavior 行為實際上能夠與任何衍生自 XmlObjectSerializer的序列化程式共同運作,其中包含 NetDataContractSerializer (在「使用獨立序列化」中會詳細描述)。 行為會呼叫其中一個 CreateSerializer
虛擬方法多載以取得序列化程式。 若要插入不同的序列化程式,請建立新的 DataContractSerializerOperationBehavior 子類別,然後同時覆寫 CreateSerializer
多載。