データ転送のアーキテクチャの概要
Windows Communication Foundation (WCF) は、メッセージング インフラストラクチャと考えることができます。 WCF は、メッセージを受信し、それらのメッセージを処理し、さらにアクションを実行するためにユーザー コードにディスパッチすることができます。また、ユーザー コードで指定されたデータからメッセージを作成し、送信先に配布することもできます。 ここでは、メッセージを処理するためのアーキテクチャと格納されるデータについて説明します。このトピックは、上級開発者を対象としています。 データを送受信する方法のより簡単なタスク指向の概要については、「 Specifying Data Transfer in Service Contracts」を参照してください。
Note
このトピックでは、WCF オブジェクト モデルを調べても明らかにはならない WCF 実装の詳細について説明します。 文書化された実装の詳細について、2 つの注意事項があります。 1 つは、説明が簡略化されているという点です。実際の実装は、最適化やその他の理由から、より複雑であることが考えられます。 もう 1 つの注意事項として、特定の実装の詳細が文書化されていても、その詳細に依存しないようにしてください。これらの詳細は、バージョン間で予告なしに変更されることがあるからです。これは、サービス リリースにおいても同様です。
基本アーキテクチャ
WCF のメッセージ処理機能の中核となるのは、Message クラスです。これについては、「メッセージ クラスの使用」で詳しく説明されています。 WCF のランタイム コンポーネントは、チャネル スタックとサービス フレームワークという 2 つの主要部分に分けることができ、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 クラス
Message クラスはメッセージの抽象表現を意図したものですが、そのデザインは SOAP メッセージと密接に関連しています。 Message には、情報の 3 つの主要部分であるメッセージ本文、メッセージ ヘッダー、およびメッセージ プロパティが含まれます。
メッセージ本文
メッセージ本文は、メッセージの実際のデータ ペイロードを表すためのものです。 メッセージ本文は、常に XML Infoset として表されます。 これは、WCF で作成または受信されるすべてのメッセージが XML 形式でなければならないということではありません。 メッセージ本文を解釈する方法を決定するのはチャネル スタックです。 チャネル スタックは、メッセージ本文を XML として出力する場合もあれば、他の形式に変換する場合もあります。また、メッセージ本文を完全に除外する場合もあります。 WCF で提供されるほとんどのバインディングでは、メッセージ本文は SOAP エンベロープの body セクションに 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
インスタンスには 2 つの文字列 ("fromCity" と "toCity") しか含まれていません。 ただし、このメッセージには、次のような XML Infoset が論理的に含まれています。
<airfareRequest>
<from>Tokyo</from>
<to>London</to>
</airfareRequest>
もちろん、このようにメッセージを作成することは、通常はありません。上記のようなメッセージは、サービス フレームワークを使用して、操作コントラクト パラメーターから作成できるためです。 さらに、 Message クラスには、静的 CreateMessage
メソッドもあります。このメソッドを使用すると、一般的な種類の内容を含むメッセージ (空のメッセージ、 DataContractSerializerによって XML にシリアル化されたオブジェクトを含むメッセージ、SOAP エラーを含むメッセージ、 XmlReaderによって表された XML を含むメッセージなど) を作成できます。
メッセージ本文からのデータの取得
メッセージ本文に格納されたデータは、主に次の 2 とおりの方法で抽出できます。
WriteBodyContents(XmlDictionaryWriter) メソッドを呼び出し、XML ライターに渡すことにより、メッセージ本文全体を一度に取得できます。 メッセージ本文全体がこのライターに書き込まれます。 メッセージ本文全体を一度に取得することを、 メッセージの書き込みとも呼びます。 書き込みは、メッセージの送信時に主にチャネル スタックによって実行されます。チャネル スタックには、通常、メッセージ本文全体にアクセスし、本文全体をエンコードして送信する部分があります。
メッセージ本文から情報を取得するもう 1 つの方法は、 GetReaderAtBodyContents() を呼び出し、XML リーダーを取得する方法です。 リーダーでメソッドを呼び出すことにより、必要に応じてメッセージ本文に順次アクセスできます。 メッセージ本文を少しずつ取得することを、 メッセージの読み取りとも呼びます。 メッセージの読み取りは、メッセージの受信時にサービス フレームワークによって主に使用されます。 たとえば、 DataContractSerializer の使用時に、サービス フレームワークは本文で XML リーダーを取得し、逆シリアル化エンジンに渡します。逆シリアル化エンジンは、要素単位でメッセージを読み取り、対応するオブジェクト グラフの構築を開始します。
メッセージ本文を取得できるのは一度だけです。 これにより、転送専用ストリームを使用することが可能になります。 たとえば、 OnWriteBodyContents(XmlDictionaryWriter) から読み取り、XML Infoset として結果を返す FileStream のオーバーライドを作成できます。 ファイルの先頭まで "巻き戻す" 必要はありません。
WriteBodyContents
メソッドと GetReaderAtBodyContents
メソッドは、メッセージ本文がこれまで一度も取得されていないことを簡単にチェックした後、それぞれ OnWriteBodyContents
と OnGetReaderAtBodyContents
を呼び出します。
WCF でのメッセージの使用
ほとんどのメッセージは、 送信 (チャネル スタックによって送信するために、サービス フレームワークが作成するメッセージ) または 受信 (チャネル スタックから到着し、サービス フレームワークが解釈するメッセージ) のいずれかに分類できます。 さらに、チャネル スタックは、バッファー モードまたはストリーミング モードで動作できます。 また、サービス フレームワークが、ストリーム プログラミング モデルを公開する場合もあれば、非ストリーム プログラミング モデルを公開する場合もあります。 この結果として生じるケースと、その実装の簡略化した詳細を次の表に示します。
メッセージの種類 | メッセージの本文データ | 書き込み (OnWriteBodyContents) 実装 | 読み取り (OnGetReaderAtBodyContents) 実装 |
---|---|---|---|
送信 (非ストリーム プログラミング モデルから作成) | メッセージの書き込みに必要なデータ (例 : オブジェクトとそのシリアル化に必要な DataContractSerializer インスタンス)* | 格納されたデータに基づいてメッセージを書き込むためのカスタム ロジック (例 : WriteObject を使用している場合に、このシリアライザーで DataContractSerializer を呼び出す)* |
OnWriteBodyContents を呼び出し、結果をバッファーに保持し、バッファーで XML リーダーを返します。 |
送信 (ストリーム プログラミング モデルから作成) | 書き込むデータを含む Stream * |
IStreamProvider 機構を使用して、格納されたストリームからデータを書き込みます*。 | OnWriteBodyContents を呼び出し、結果をバッファーに保持し、バッファーで XML リーダーを返します。 |
ストリーミング チャネル スタックからの受信 | ネットワーク上で到着したデータを表す Stream オブジェクトと、このオブジェクトに配置された XmlReader |
XmlReader を使用して、格納された WriteNode からコンテンツを書き込みます。 |
格納された XmlReader を返します。 |
非ストリーミング チャネル スタックからの受信 | 本文データを格納するバッファーと、このバッファーに配置された XmlReader |
XmlReader を使用して、格納された WriteNode からコンテンツを書き込みます。 |
格納された lang を返します。 |
* これらの項目は、Message
サブクラスに直接実装されるのではなく、BodyWriter クラスのサブクラスに実装されます。 BodyWriter の詳細については、「メッセージ クラスの使用」を参照してください。
メッセージ ヘッダー
メッセージには、ヘッダーを含めることができます。 ヘッダーは、名前、名前空間、および他の複数のプロパティに関連付けられた XML Infoset で論理的に構成されます。 メッセージ ヘッダーには、 Headers
の Messageプロパティを使用してアクセスします。 各ヘッダーは、 MessageHeader クラスによって表されます。 通常、SOAP メッセージを使用するように構成されたチャネル スタックを使用している場合、メッセージ ヘッダーは SOAP メッセージ ヘッダーにマップされます。
メッセージ ヘッダーへの情報の配置と、メッセージ ヘッダーからの情報の抽出は、メッセージ本文を使用する場合と似ています。 ストリーミングがサポートされていないため、プロセスは若干簡略化されます。 ヘッダーは常に強制的にバッファーに保持されるため、同じヘッダーの内容に何度もアクセスすることが可能であり、各ヘッダーに任意の順序でアクセスできます。 ヘッダーで XML リーダーを取得するために使用できる汎用の機構はありませんが、このような機能を備えた読み取り可能なヘッダーを表す MessageHeader
サブクラスが WCF の内部に存在します。 この種の MessageHeader
は、カスタム アプリケーション ヘッダーを持つメッセージが到着したときにチャネル スタックによって作成されます。 これにより、サービス フレームワークは、逆シリアル化エンジン ( DataContractSerializerなど) を使用してこれらのヘッダーを解釈できます。
詳細については、「メッセージ クラスの使用」を参照してください。
メッセージ プロパティ
メッセージには、プロパティを含めることができます。 "プロパティ" とは、文字列名に関連付けられた任意の .NET Framework オブジェクトです。 プロパティには、 Properties
の Message
プロパティからアクセスします。
通常、メッセージ本文とメッセージ ヘッダーは、それぞれ SOAP 本文および SOAP ヘッダーにマップされますが、メッセージ プロパティがメッセージと共に送信または受信されることは通常ありません。 メッセージ プロパティは、チャネル スタック内のさまざまなチャネル間、およびチャネル スタックとサービス モデルの間で、メッセージに関するデータを渡す通信機構として主に存在します。
たとえば、WCF の一部として含まれる HTTP トランスポート チャネルでは、クライアントに応答を送信するときに、"404 (ページが見つかりません)" や "500 (内部サーバー エラー)" などのさまざまな HTTP ステータス コードを生成できます。 これにより、応答メッセージを送信する前に、HttpResponseMessageProperty 型のオブジェクトを格納する "httpResponse" というプロパティが Message
の Properties
に含まれているかどうかがチェックされます。 このようなプロパティが見つかった場合、 StatusCode プロパティを調べ、そのステータス コードを使用します。 該当のプロパティが見つからなかった場合は、既定の "200 (OK)" コードが使用されます。
詳細については、「メッセージ クラスの使用」を参照してください。
メッセージ全体
これまで、メッセージのさまざまな部分に個別にアクセスするためのメソッドについて説明してきましたが、 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
インスタンスに変換したりする役割を担います。
チャネル スタックは、一連の順序付けられた 1 つ以上のチャネルで構成されます。 送信 Message
インスタンスは、スタック内の最初のチャネル ( "最上位チャネル"とも呼ばれます) に渡され、このチャネルからスタック内の 1 つ下のチャネルに渡されます。以降、同様にスタック内の 1 つ下のチャネルに順次渡されていきます。 メッセージは、 "トランスポート チャネル"と呼ばれる最後のチャネルで終了します。 受信メッセージはトランスポート チャネルから始まり、スタック内の下位のチャネルから上位のチャネルに順次渡されていきます。 通常、メッセージは最上位チャネルからサービス フレームワークに渡されます。 これは、アプリケーション メッセージの通常のパターンですが、若干動作が異なるチャネルもあります。たとえば、上のチャネルからメッセージが渡されることなく、独自のインフラストラクチャ メッセージを送信する場合があります。
メッセージがスタックを通過するときに、チャネルではさまざまな方法でメッセージを処理できます。 最も一般的な処理は、送信メッセージにヘッダーを追加し、受信メッセージのヘッダーを読み取ることです。 たとえば、チャネルでメッセージのデジタル署名を計算し、ヘッダーとして追加できます。 また、受信メッセージのこのデジタル署名ヘッダーを検査し、有効な署名のないメッセージがチャネル スタック内の上位のチャネルに渡されないようにブロックすることもできます。 多くの場合、チャネルはメッセージ プロパティの設定や検査も行います。 通常、メッセージ本文は変更されませんが、変更は可能です。たとえば、WCF セキュリティ チャネルではメッセージ本文を暗号化できます。
トランスポート チャネルとメッセージ エンコーダー
他のチャネルによって変更された送信 Messageを実際に何らかのアクションに変換するのは、スタック内の最下位チャネルです。 受信側では、このチャネルがアクションを Message
に変換して、他のチャネルが処理できるようにします。
既に説明したように、アクションはさまざまです。たとえば、各種プロトコル上でのネットワーク パケットの送信/受信、データベースでのメッセージの読み取り/書き込み、メッセージ キューへのメッセージの配置/キューからのメッセージの削除などがあります。 これらのアクションには、共通点が 1 つあります。それは、これらでは、WCFMessage
インスタンスと、送信、受信、読み取り、書き込み、キューへの配置、またはキューからの削除が可能な実際のバイト グループとの間の変換が必要であるということです。 Message
をバイト グループに変換するプロセスは エンコードと呼ばれ、バイト グループから Message
を作成する逆のプロセスは デコードと呼ばれます。
ほとんどのトランスポート チャネルでは、 メッセージ エンコーダー と呼ばれるコンポーネントを使用して、エンコードとデコードの処理を行います。 メッセージ エンコーダーは、 MessageEncoder クラスのサブクラスです。 MessageEncoder
には、 ReadMessage
とバイト グループとの間の変換を行う WriteMessage
メソッドと Message
メソッドのさまざまなオーバーロードが含まれます。
送信側では、バッファー トランスポート チャネルが上のチャネルから受け取った Message
オブジェクトを WriteMessage
に渡します。 バッファー トランスポート チャネルはバイト配列を取得し、アクション (これらのバイトを有効な TCP パケットとしてパッケージングし、適切な送信先に送信するなど) を実行するために使用します。 ストリーミング トランスポート チャネルは、(たとえば、送信 TCP 接続で) まず Stream
を作成します。次に、この Stream
と送信に必要な Message
の両方を適切な WriteMessage
オーバーロードに渡し、このオーバーロードによってメッセージが書き込まれます。
受信側では、バッファー トランスポート チャネルは、(たとえば、受信 TCP パケットから) 受信バイトを配列に抽出し、 ReadMessage
を呼び出して、チャネル スタックの上位に渡すことができる Message
オブジェクトを取得します。 ストリーミング トランスポート チャネルは、 Stream
オブジェクト (受信 TCP 接続のネットワーク ストリームなど) を作成し、 ReadMessage
に渡して Message
オブジェクトを取得します。
トランスポート チャネルとメッセージ エンコーダーの分離は必須ではありません。つまり、メッセージ エンコーダーを使用しないトランスポート チャネルの作成が可能です。 ただし、この分離には、構成しやすいという利点があります。 トランスポート チャネルで基本 MessageEncoder だけが使用されている限り、WCF のメッセージ エンコーダーを使用することも、サードパーティのメッセージ エンコーダーを使用することもできます。 同様に、通常はどのトランスポート チャネルでも同じエンコーダーを使用できます。
メッセージ エンコーダーの動作
エンコーダーの一般的な動作を記述する場合、次の 4 つのケースについて検討すると有益です。
操作 | コメント |
---|---|
エンコード (バッファー) | バッファー モードでは、通常、エンコーダーは可変サイズのバッファーを作成し、このバッファーに XML ライターを作成します。 エンコーダーは、エンコードするメッセージに対して WriteMessage(XmlWriter) を呼び出してヘッダーを書き込みます。次に、このトピックの WriteBodyContents(XmlDictionaryWriter)に関するセクションで説明したように、 Message を使用して本文を書き込みます。 その後、トランスポート チャネルで使用できるように、(バイト配列として表される) バッファーの内容が返されます。 |
エンコード (ストリーミング) | ストリーミング モードでは、動作が上記に似ていますが、より単純です。 バッファーは必要ありません。 通常、XML ライターがストリームに作成され、このライターに書き込むために WriteMessage(XmlWriter) に対して Message が呼び出されます。 |
デコード (バッファー) | バッファー モードでデコードする場合、通常、バッファーされたデータを格納する特殊な Message サブクラスが作成されます。 メッセージのヘッダーが読み取られ、メッセージ本文に配置する XML リーダーが作成されます。 これは、 GetReaderAtBodyContents()で返されるリーダーです。 |
デコード (ストリーミング) | ストリーミング モードでデコードする場合、通常、特殊な Message サブクラスが作成されます。 ストリームは、すべてのヘッダーを読み取り、メッセージ本文に配置できる位置まで進められます。 次に、ストリームに XML リーダーが作成されます。 これは、 GetReaderAtBodyContents()で返されるリーダーです。 |
エンコーダーでは、他の機能も実行できます。 たとえば、エンコーダーは XML リーダーとライターをプールできます。 新しい XML リーダーまたはライターが必要になるたびに作成すると負荷がかかります。 したがって、通常、エンコーダーは構成可能なサイズのリーダーのプールとライターのプールを保持しています。 前述のエンコーダーの動作の説明で、"XML リーダー/ライターを作成する" というフレーズが使用されているときは、通常、"プールから取得し、プールされているものを使用できない場合に作成する" ことを意味します。エンコーダー (およびデコード時に作成される Message
サブクラス) には、リーダーとライターが不要になったら (Message
を閉じたときなど) プールに戻すためのロジックが含まれています。
WCF には、3 つのメッセージ エンコーダーが用意されていますが、さらにカスタム エンコーダーを作成することもできます。 用意されているエンコーダーは、Text、Binary、および MTOM (Message Transmission Optimization Mechanism) の 3 種類です。 これらの詳細については、「 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 ライターやバイナリ XML ライターは、このメソッドをすぐに呼び出し、開始タグと終了タグの間にストリーミングされたコンテンツを書き込むことができます。 MTOM ライターは、メッセージの適切な部分を書き込む準備ができたときに、後で GetStream() を呼び出すことができます。
サービス フレームワークでのデータの表現
このトピックの「基本アーキテクチャ」セクションで説明したように、サービス フレームワークは WCF の一部であり、特に、メッセージ データのユーザー フレンドリなプログラミング モデルと実際の Message
インスタンス間で変換を行うという役割を果たします。 通常、サービス フレームワークでは、メッセージ交換は OperationContractAttribute 属性でマークされた .NET Framework メソッドとして表されます。 このメソッドは複数のパラメーターを取得でき、戻り値または出力パラメーター (または両方) を返すことができます。 サービス側では、入力パラメーターは受信メッセージを表し、戻り値と出力パラメーターは送信メッセージを表します。 クライアント側では、この逆になります。 パラメーターと戻り値を使用してメッセージを記述するためのプログラミング モデルの詳細については、「 Specifying Data Transfer in Service Contracts」を参照してください。 ここでは、概要を簡単に説明します。
プログラミング モデル
WCF サービス フレームワークでは、メッセージを記述するための次の 5 種類のプログラミング モデルがサポートされています。
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
の例では、双方向メッセージ交換パターンが記述されています。 メッセージは操作から返されますが、このメッセージは空です。 操作からエラーを返すことができます。 "SetLightbulb" の例では、メッセージ交換パターンは一方向であるため、記述する送信メッセージはありません。 この場合、サービスはクライアントにステータスを通知できません。
2.Message クラスの直接使用
操作コントラクトで Message クラス (またはサブクラスのいずれか) を直接使用できます。 この場合、サービス フレームワークは、操作からチャネル スタックおよびチャネル スタックから操作に Message
を渡すだけであり、それ以上の処理は行いません。
Message
を直接使用するケースとして、主に 2 つのケースがあります。 1 つは、他のプログラミング モデルでは、メッセージを記述できるだけの柔軟性を得ることができない高度なシナリオで使用します。 たとえば、ディスク上のファイルを使用して、ファイルのプロパティがメッセージ ヘッダーになり、ファイルの内容がメッセージ本文になるようにメッセージを記述することが必要になる場合があります。 これは、次のように作成できます。
public class FileMessage : Message
// Code not shown.
Public Class FileMessage
Inherits Message
' Code not shown.
操作コントラクトで Message
を一般的に使用するもう 1 つのケースは、サービスがメッセージの特定の内容に留意しているわけではなく、ブラック ボックスと同様にメッセージを処理する場合です。 たとえば、メッセージを他の複数の受信者に転送するサービスを使用することがあります。 コントラクトは、次のように作成できます。
[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」を参照してください。 基本的に、単一の .NET Framework 型によってメッセージ全体が表されます。この型では、MessageBodyMemberAttribute や MessageHeaderAttribute などの属性を使用して、メッセージ コントラクト クラスのどの部分をメッセージのどの部分にマップする必要があるかを示します。
メッセージ コントラクトは、結果として生成される Message
インスタンスに対してさまざまな制御を行うことができます (ただし、 Message
クラスを直接使用した場合と同様に制御できるわけではありません)。 たとえば、多くの場合、メッセージ本文は情報の複数の部分で構成され、各部分は独自の XML 要素によって表されます。 これらの要素は、本文に直接出現することも (ベア モード)、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)
サービス フレームワークは、情報の 3 つの部分 (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」) に記載されています。
メッセージ フォーマッタ
前述の各プログラミング モデルは、 "メッセージ フォーマッタ" と呼ばれるコンポーネントをサービス フレームワークにプラグインすることによってサポートされます。 メッセージ フォーマッタは、IClientMessageFormatter インターフェイス (クライアントで使用)、または IDispatchMessageFormatter インターフェイス (WCF サービス クライアントで使用)、あるいはこの両方を実装する型です。
通常、メッセージ フォーマッタは動作によってプラグインされます。 たとえば、 DataContractSerializerOperationBehavior は、データ コントラクト メッセージ フォーマッタをプラグインします。 これを行うには、サービス側では Formatter メソッドで ApplyDispatchBehavior(OperationDescription, DispatchOperation) を適切なフォーマッタに設定します。クライアント側では、 Formatter メソッドで ApplyClientBehavior(OperationDescription, ClientOperation) を適切なフォーマッタに設定します。
メッセージ フォーマッタが実装できるメソッドを次の表に示します。
インターフェイス | Method | アクション |
---|---|---|
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
の 2 つがサポートされています。 また、カスタム シリアライザーを作成することもできます。 ただし、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
の両方のオーバーロードをオーバーライドします。