Windows Communication Foundation (WCF) は、メッセージング インフラストラクチャと考えることができます。 メッセージを受信し、処理し、ユーザー コードにディスパッチしてさらにアクションを実行したり、ユーザー コードによって指定されたデータからメッセージを作成して宛先に配信したりできます。 このトピックは、高度な開発者を対象としており、メッセージと含まれているデータを処理するためのアーキテクチャについて説明します。 データの送受信方法のタスク指向の簡単なビューについては、「 サービス コントラクトでのデータ転送の指定」を参照してください。
注
このトピックでは、WCF オブジェクト モデルを調べることで表示されない WCF 実装の詳細について説明します。 文書化された実装の詳細については、2 つの注意が必要です。 最初に、説明は簡略化されています。実際の実装は、最適化やその他の理由により、より複雑になる可能性があります。 2 つ目は、特定の実装の詳細 (ドキュメントに記載されている場合でも) に依存しないようにする必要があります。これは、バージョンからバージョンへの通知なく、またはサービス リリースでも変更される可能性があるためです。
基本的なアーキテクチャ
WCF メッセージ処理機能の中核となるのは、 Message クラスです。詳細については、「 メッセージ クラスの使用」を参照してください。 WCF のランタイム コンポーネントは、チャネル スタックとサービス フレームワークという 2 つの主要な部分に分けられ、 Message クラスが接続ポイントになります。
チャネル スタックは、有効な Message インスタンスと、メッセージ データの送受信に対応するアクションの間で変換を行います。 送信側では、チャネル スタックは有効な Message インスタンスを受け取り、処理後に、メッセージの送信に論理的に対応するアクションを実行します。 このアクションは、TCP または HTTP パケットの送信、メッセージ キューでのメッセージのキューの作成、データベースへのメッセージの書き込み、ファイル共有への保存、または実装に応じたその他のアクションです。 最も一般的なアクションは、ネットワーク プロトコル経由でメッセージを送信することです。 受信側では、その逆が発生します。アクションは検出され (TCP または HTTP パケットが到着する場合もあれば、その他のアクションの場合もあります)、処理後、チャネル スタックはこのアクションを有効な Message インスタンスに変換します。
WCF は、 Message クラスとチャネル スタックを直接使用して使用できます。 ただし、これを行うことは困難で時間がかかります。 さらに、 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 エンベロープの本文セクションの XML コンテンツとして表されます。
Message
クラスには、本文を表す XML データを含むバッファーが必ずしも含まれていないことに注意することが重要です。 論理的には、 Message
には XML Infoset が含まれていますが、この Infoset は動的に構築され、メモリ内に物理的に存在しない可能性があります。
メッセージ本文へのデータの配置
メッセージ本文にデータを配置する均一なメカニズムはありません。
Message クラスには、XmlDictionaryWriterを受け取る抽象メソッド (OnWriteBodyContents(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 情報セットが含まれています。
<airfareRequest>
<from>Tokyo</from>
<to>London</to>
</airfareRequest>
もちろん、通常、この方法ではメッセージを作成しません。これは、サービス フレームワークを使用して、操作コントラクト パラメーターから上記のようなメッセージを作成できるためです。 さらに、 Message クラスには、共通の種類のコンテンツを含むメッセージを作成するために使用できる静的な CreateMessage
メソッドがあります。空のメッセージ、 DataContractSerializerで XML にシリアル化されたオブジェクトを含むメッセージ、SOAP エラーを含むメッセージ、 XmlReaderで表される XML を含むメッセージなどです。
メッセージ本文からデータを取得する
メッセージ本文に格納されているデータは、主に次の 2 つの方法で抽出できます。
WriteBodyContents(XmlDictionaryWriter) メソッドを呼び出して XML ライターを渡すことで、メッセージ本文全体を一度に取得できます。 完全なメッセージ本文は、このライターに書き出されます。 メッセージ本文全体を一度に取得することは、 メッセージの書き込みとも呼ばれます。 書き込みは、メッセージを送信するときに主にチャネル スタックによって行われます。通常、チャネル スタックの一部では、メッセージ本文全体にアクセスし、エンコードして送信します。
メッセージ本文から情報を取得するもう 1 つの方法は、 GetReaderAtBodyContents() を呼び出して XML リーダーを取得することです。 メッセージ本文は、リーダーでメソッドを呼び出すことによって、必要に応じて順番にアクセスできます。 メッセージ本文を 1 つずつ取得することは、 メッセージの読み取りとも呼ばれます。 メッセージの読み取りは、主にメッセージを受信するときにサービス フレームワークによって使用されます。 たとえば、 DataContractSerializer が使用されている場合、サービス フレームワークは本文に対して XML リーダーを取得し、逆シリアル化エンジンに渡します。これにより、メッセージ要素ごとの読み取りと、対応するオブジェクト グラフの構築が開始されます。
メッセージ本文は 1 回だけ取得できます。 これにより、前方専用ストリームを操作できます。 たとえば、FileStreamから読み取り、結果を XML Infoset として返すOnWriteBodyContents(XmlDictionaryWriter)オーバーライドを記述できます。 ファイルの先頭に "巻き戻す" 必要はありません。
WriteBodyContents
メソッドとGetReaderAtBodyContents
メソッドは、メッセージ本文がこれまでに取得されていないことを確認し、それぞれOnWriteBodyContents
またはOnGetReaderAtBodyContents
を呼び出します。
WCF でのメッセージの使用法
ほとんどのメッセージは 、送信 (チャネル スタックによって送信されるサービス フレームワークによって作成されたもの) または 受信 (チャネル スタックから到着し、サービス フレームワークによって解釈されるメッセージ) として分類できます。 さらに、チャネル スタックはバッファー モードまたはストリーミング モードで動作できます。 サービス フレームワークは、ストリーミングまたは非ストリーム プログラミング モデルを公開することもできます。 これにより、次の表に示すケースと、実装の簡略化された詳細が示されます。
メッセージの種類 | メッセージ内の本文データ | Write (OnWriteBodyContents) の実装 | Read (OnGetReaderAtBodyContents) の実装 |
---|---|---|---|
送信、非ストリーミング プログラミング モデルから作成 | メッセージを書き込むのに必要なデータ (オブジェクトやシリアル化に必要な DataContractSerializer インスタンスなど) * | 保存されたデータに基づいてメッセージを書き出すカスタム ロジック (たとえば、使用中のシリアライザーの場合はDataContractSerializer でWriteObject を呼び出す) * |
OnWriteBodyContents を呼び出し、結果をバッファーに格納し、バッファーに XML リーダーを返します |
ストリーミング プログラミング モデルから作成された送信 | 書き込むデータを含む Stream * |
IStreamProvider メカニズムを使用して、格納されたストリームからデータを書き出します* |
OnWriteBodyContents を呼び出し、結果をバッファーに格納し、バッファーに XML リーダーを返します |
ストリーミング チャネル スタックからの受信 | ネットワーク経由で受信するデータを表す Stream オブジェクトで、その上に XmlReader があります。 |
を使用して、格納されている XmlReader から内容を書き出します。 WriteNode |
格納されている値を返します。 XmlReader |
非ストリーミング チャネル スタックからの受信 |
XmlReader を持つ本文データを格納するバッファー |
を使用して、格納されている XmlReader から内容を書き出します。 WriteNode |
格納されている言語を返します。 |
* これらの項目は、 Message
サブクラスではなく、 BodyWriter クラスのサブクラスに直接実装されます。
BodyWriterの詳細については、「メッセージ クラスの使用」を参照してください。
メッセージ ヘッダー
メッセージにはヘッダーが含まれている場合があります。 ヘッダーは、名前、名前空間、およびその他のいくつかのプロパティに関連付けられている XML Infoset で論理的に構成されます。 メッセージ ヘッダーには、Messageの Headers
プロパティを使用してアクセスします。 各ヘッダーは、 MessageHeader クラスによって表されます。 通常、SOAP メッセージを操作するように構成されたチャネル スタックを使用する場合、メッセージ ヘッダーは SOAP メッセージ ヘッダーにマップされます。
情報をメッセージ ヘッダーに配置し、そこから情報を抽出することは、メッセージ本文の使用と似ています。 ストリーミングはサポートされていないため、プロセスはやや簡略化されています。 同じヘッダーの内容に複数回アクセスでき、ヘッダーは任意の順序でアクセスでき、ヘッダーは常にバッファーに格納されます。 ヘッダーに対して XML リーダーを取得するために使用できる汎用メカニズムはありませんが、このような機能を備えた読み取り可能なヘッダーを表す WCF の内部には MessageHeader
サブクラスがあります。 この種類の MessageHeader
は、カスタム アプリケーション ヘッダーを含むメッセージが受信されたときにチャネル スタックによって作成されます。 これにより、サービス フレームワークは、 DataContractSerializerなどの逆シリアル化エンジンを使用して、これらのヘッダーを解釈できます。
詳細については、「 メッセージ クラスの使用」を参照してください。
メッセージ プロパティ
メッセージにはプロパティが含まれている場合があります。
プロパティは、文字列名に関連付けられている任意の .NET Framework オブジェクトです。 プロパティには、Message
の Properties
プロパティを使用してアクセスします。
メッセージ本文とメッセージ ヘッダー (通常は SOAP 本文と SOAP ヘッダーにそれぞれマップされます) とは異なり、通常、メッセージのプロパティはメッセージと共に送受信されません。 メッセージ プロパティは、主にチャネル スタック内のさまざまなチャネル間、およびチャネル スタックとサービス モデルの間でメッセージに関するデータを渡す通信メカニズムとして存在します。
たとえば、WCF の一部として含まれる HTTP トランスポート チャネルでは、クライアントに応答を送信するときに、"404 (見つかりません)" や "500 (内部サーバー エラー)" などのさまざまな HTTP 状態コードを生成できます。 応答メッセージを送信する前に、Message
のProperties
に、HttpResponseMessageProperty型のオブジェクトを含む "httpResponse" というプロパティが含まれているかどうかを確認します。 このようなプロパティが見つかった場合は、 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 固有のマッピングを完全に無効にすることが望ましい場合があります。
これらの目的のために、Message
にVersion
プロパティが提供されます。 メッセージを書き出すときに使用する SOAP バージョンに設定することも、SOAP 固有のマッピングを防ぐために None
に設定することもできます。
Version
プロパティが None
に設定されている場合、メッセージ全体を操作するメソッドは、メッセージが本文のみで構成されているかのように動作します。たとえば、WriteMessage
は、上記の複数の手順を実行するのではなく、単にWriteBodyContents
を呼び出します。 受信メッセージでは、 Version
が自動検出され、正しく設定されることが予想されます。
チャネル スタック
チャンネル
前述のように、チャネル スタックは、送信 Message インスタンスを何らかのアクション (ネットワーク経由でパケットを送信するなど) に変換したり、何らかのアクション (ネットワーク パケットの受信など) を受信 Message
インスタンスに変換したりする役割を担います。
チャネル スタックは、シーケンスで並べ替えられた 1 つ以上のチャネルで構成されます。 送信 Message
インスタンスは、スタック内の最初のチャネル ( 最上位チャネルとも呼ばれます) に渡され、スタック内の次のチャネルに渡されます。 メッセージは、トランスポート チャネルと呼ばれる最後のチャネルで終了 します。 受信メッセージはトランスポート チャネルで送信され、チャネルからスタック上のチャネルに渡されます。 一番上のチャネルから、通常、メッセージはサービス フレームワークに渡されます。 これはアプリケーション メッセージの通常のパターンですが、一部のチャネルは動作が若干異なる場合があります。たとえば、上記のチャネルからメッセージを渡さずに独自のインフラストラクチャ メッセージを送信する場合があります。
チャネルは、スタックを通過するさまざまな方法でメッセージを操作できます。 最も一般的な操作は、送信メッセージにヘッダーを追加し、受信メッセージのヘッダーを読み取る操作です。 たとえば、チャネルはメッセージのデジタル署名を計算し、ヘッダーとして追加できます。 チャネルでは、受信メッセージでこのデジタル署名ヘッダーを検査し、有効な署名を持たないメッセージがチャネル スタックを上げるのをブロックすることもできます。 チャネルでは、多くの場合、メッセージのプロパティを設定または検査します。 通常、メッセージ本文は変更されませんが、WCF セキュリティ チャネルではメッセージ本文を暗号化できます。
トランスポート チャネルとメッセージ エンコーダー
スタックの一番下のチャネルは、他のチャネルによって変更された送信 Messageを実際に何らかのアクションに変換する役割を担います。 受信側では、これは、何らかのアクションを他のチャネルが処理する Message
に変換するチャネルです。
前述のように、さまざまなプロトコルを介したネットワーク パケットの送受信、データベース内のメッセージの読み取りまたは書き込み、メッセージ キューキュー内のメッセージのキューまたはデキューなど、さまざまなアクションが提供されますが、いくつかの例があります。 これらのアクションには共通する 1 つの共通点があります。WCFMessage
インスタンスと、送信、受信、読み取り、書き込み、キューに登録、またはデキューできる実際のバイト グループの間の変換が必要です。
Message
をバイトのグループに変換するプロセスはエンコードと呼ばれ、バイトのグループからMessage
を作成する逆のプロセスはデコードと呼ばれます。
ほとんどのトランスポート チャネルでは、 メッセージ エンコーダー と呼ばれるコンポーネントを使用して、エンコードとデコードの処理を実行します。 メッセージ エンコーダーは、 MessageEncoder クラスのサブクラスです。
MessageEncoder
には、Message
とバイト のグループ間で変換するためのさまざまなReadMessage
およびWriteMessage
メソッドのオーバーロードが含まれています。
送信側では、バッファリング トランスポート チャネルは、その上のチャネルから受信した Message
オブジェクトを WriteMessage
に渡します。 バイトの配列が返され、その後、そのアクションを実行するために使用されます (これらのバイトを有効な TCP パケットとしてパッケージ化し、正しい宛先に送信するなど)。 ストリーミング トランスポート チャネルは、最初にStream
を作成し (たとえば、送信 TCP 接続経由で)、メッセージを書き出す適切なWriteMessage
オーバーロードに送信する必要があるStream
とMessage
の両方を渡します。
受信側では、バッファリング トランスポート チャネルは受信バイト (受信 TCP パケットなど) を配列に抽出し、 ReadMessage
を呼び出して、チャネル スタックをさらに上に渡すことができる Message
オブジェクトを取得します。 ストリーミング トランスポート チャネルは、Stream
オブジェクト (受信 TCP 接続経由のネットワーク ストリームなど) を作成し、Message
オブジェクトを取得するためにReadMessage
に渡します。
トランスポート チャネルとメッセージ エンコーダーの分離は必須ではありません。メッセージ エンコーダーを使用しないトランスポート チャネルを記述できます。 しかし、この分離の利点は、組成物の容易さである。 トランスポート チャネルが基本 MessageEncoderのみを使用している限り、WCF またはサード パーティのメッセージ エンコーダーで動作できます。 同様に、通常、同じエンコーダーを任意のトランスポート チャネルで使用できます。
メッセージ エンコーダー操作
エンコーダーの一般的な動作を説明するには、次の 4 つのケースを考慮すると便利です。
オペレーション | コメント |
---|---|
エンコード、バッファー | バッファー モードでは、エンコーダーは通常、可変サイズのバッファーを作成し、その上に XML ライターを作成します。 次に、エンコードされているメッセージの WriteMessage(XmlWriter) を呼び出します。このメッセージは、このトピックの前のセクションで説明したように、 WriteBodyContents(XmlDictionaryWriter)を使用してヘッダーと本文を書き出します Message 。 その後、バッファーの内容 (バイトの配列として表されます) が返され、トランスポート チャネルが使用されます。 |
エンコード、ストリーミング | ストリーミング モードでは、操作は上記に似ていますが、より簡単です。 バッファーは必要ありません。 通常、XML ライターはストリームを介して作成され、 WriteMessage(XmlWriter) はこのライターに書き出すために Message で呼び出されます。 |
デコード、バッファー | バッファー モードでデコードする場合、通常、バッファーに格納されたデータを含む特別な Message サブクラスが作成されます。 メッセージのヘッダーが読み取られ、メッセージ本文に配置された XML リーダーが作成されます。 これは、 GetReaderAtBodyContents()で返されるリーダーです。 |
デコード、ストリーミング | ストリーミング モードでデコードする場合、通常は特別な Message サブクラスが作成されます。 ストリームは、すべてのヘッダーを読み取り、メッセージ本文に配置するのに十分なだけ高度です。 その後、ストリームを介して XML リーダーが作成されます。 これは、 GetReaderAtBodyContents()で返されるリーダーです。 |
エンコーダーは、他の機能も実行できます。 たとえば、エンコーダーは XML リーダーとライターをプールできます。 必要なたびに新しい XML リーダーまたはライターを作成するとコストがかかります。 したがって、エンコーダーは通常、リーダーのプールと構成可能なサイズのライターのプールを維持します。 前に説明したエンコーダー操作の説明では、"XML リーダー/ライターの作成" という語句が使用されるたびに、通常は "プールから 1 つ取得するか、使用できない場合は作成する" を意味します。エンコーダー (およびデコード中に作成される Message
サブクラス) には、リーダーとライターが不要になったら (たとえば、 Message
が閉じられたときに) プールに返すロジックが含まれています。
WCF には 3 つのメッセージ エンコーダーが用意されていますが、追加のカスタム型を作成することはできます。 指定された型は、Text、Binary、Message Transmission Optimization Mechanism (MTOM) です。 詳細については、「 メッセージ エンコーダーの選択」を参照してください。
IStreamProvider インターフェイス
ストリーム本文を含む送信メッセージを XML ライターに書き込む場合、 Message は、 OnWriteBodyContents(XmlDictionaryWriter) 実装で次のような一連の呼び出しを使用します。
ストリームの前に必要な情報 (たとえば、開始 XML タグ) を書き込みます。
ストリームを書き込みます。
ストリームの後に情報を書き込みます (たとえば、終了 XML タグ)。
これは、テキスト XML エンコードに似たエンコードで適切に機能します。 ただし、一部のエンコードでは、XML Infoset 情報 (XML 要素の開始と終了のタグなど) が要素内に含まれるデータと一緒に配置されません。 たとえば、MTOM エンコードでは、メッセージは複数の部分に分割されます。 1 つのパーツには XML インフォセットが含まれています。XML 情報セットには、実際の要素の内容に対する他のパーツへの参照が含まれている場合があります。 XML Infoset は通常、ストリーミングされたコンテンツと比較して小さいため、Infoset をバッファーに格納し、書き出して、ストリーミングされた方法でコンテンツを書き込むのが理にかなっています。 つまり、終了要素タグが書き込まれるまでに、ストリームがまだ書き出されていないはずです。
この目的のために、 IStreamProvider インターフェイスが使用されます。 インターフェイスには、書き込むストリームを返す GetStream() メソッドがあります。 OnWriteBodyContents(XmlDictionaryWriter)でストリームメッセージ本文を書き出す正しい方法は次のとおりです。
ストリームの前に必要な情報 (たとえば、開始 XML タグ) を書き込みます。
書き込むストリームを返す
IStreamProvider
実装を使用して、IStreamProviderを受け取るXmlDictionaryWriterに対してWriteValue
オーバーロードを呼び出します。ストリームの後に情報を書き込みます (たとえば、終了 XML タグ)。
この方法では、XML ライターは、 GetStream() を呼び出してストリーミング データを書き出すタイミングを選択できます。 たとえば、テキスト XML ライターとバイナリ XML ライターは、それをすぐに呼び出し、開始タグと終了タグの間にストリーミングされたコンテンツを書き出します。 MTOM ライターは、メッセージの適切な部分を書き込む準備ができたら、後で GetStream() を呼び出すことができます。
サービス フレームワークでのデータの表現
このトピックの「基本的なアーキテクチャ」セクションで説明されているように、サービス フレームワークは WCF の一部であり、特に、メッセージ データと実際の Message
インスタンスのわかりやすいプログラミング モデルの間で変換を行います。 通常、メッセージ交換はサービス フレームワークで、 OperationContractAttribute 属性でマークされた .NET Framework メソッドとして表されます。 このメソッドは、いくつかのパラメーターを受け取ることができ、戻り値または出力パラメーター (またはその両方) を返すことができます。 サービス側では、入力パラメーターは受信メッセージを表し、戻り値と出力パラメーターは送信メッセージを表します。 クライアント側では、逆は true です。 パラメーターと戻り値を使用してメッセージを記述するためのプログラミング モデルについては、「 サービス コントラクトでのデータ転送の指定」で詳しく説明します。 ただし、このセクションでは簡単な概要について説明します。
プログラミング モデル
WCF サービス フレームワークでは、メッセージを記述するための 5 つの異なるプログラミング モデルがサポートされています。
1. 空のメッセージ
これは最も簡単なケースです。 空の受信メッセージを記述するには、入力パラメーターを使用しないでください。
[OperationContract]
int GetCurrentTemperature();
<OperationContract()> Function GetCurrentTemperature() As Integer
空の送信メッセージを記述するには、void 戻り値を使用し、out パラメーターを使用しないでください。
[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
例では、双方向メッセージ交換パターンについて説明します。 操作からメッセージが返されますが、空です。 操作からエラーを返すことができます。 "Lightbulb の設定" の例では、メッセージ交換パターンは一方向であるため、説明する送信メッセージはありません。 この場合、サービスはクライアントに状態を伝えることはできません。
2. Message クラスを直接使用する
Message クラス (またはそのサブクラスの 1 つ) を操作コントラクトで直接使用できます。 この場合、サービス フレームワークは操作からチャネル スタックに Message
を渡すだけで、その逆も処理されません。
Message
を直接使用する主なユース ケースは 2 つあります。 これは、他のプログラミング モデルでメッセージを記述するのに十分な柔軟性がない場合に、高度なシナリオに使用できます。 たとえば、ディスク上のファイルを使用してメッセージを記述し、ファイルのプロパティがメッセージ ヘッダーになり、ファイルの内容がメッセージ本文になります。 その後、次のような内容を作成できます。
public class FileMessage : Message
// Code not shown.
Public Class FileMessage
Inherits Message
' Code not shown.
操作コントラクトでの Message
の 2 番目の一般的な用途は、サービスが特定のメッセージの内容を気にせず、メッセージをブラック ボックスのように処理する場合です。 たとえば、メッセージを他の複数の受信者に転送するサービスがあるとします。 コントラクトは次のように記述できます。
[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 には、 メッセージ コントラクトと呼ばれるメッセージを記述するための宣言型プログラミング モデルが用意されています。 このモデルの詳細については、「 メッセージ コントラクトの使用」を参照してください。 基本的に、メッセージ全体は、 MessageBodyMemberAttribute や MessageHeaderAttribute などの属性を使用して、メッセージ コントラクト クラスのどの部分をメッセージのどの部分にマップするかを記述する単一の .NET Framework 型で表されます。
メッセージ コントラクトは、結果として得られる 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
またはそのサブクラスの 1 つを使用するか、メッセージ コントラクトの唯一のメッセージ本文部分として使用することは、上記のプログラミング モデルとは別のプログラミング モデルと見なすことができます。 この方法で Stream
を使用することは、独自のストリーミング互換の Message
サブクラスを記述するだけで、コントラクトがストリーミング形式で使用されることを保証する唯一の方法です。 詳細については、「 大きなデータとストリーミング」を参照してください。
この方法で Stream
またはそのサブクラスの 1 つが使用されている場合、シリアライザーは呼び出されません。 送信メッセージの場合、特殊なストリーミング Message
サブクラスが作成され、 IStreamProvider インターフェイスのセクションで説明されているようにストリームが書き出されます。 受信メッセージの場合、サービス フレームワークは受信メッセージに対して Stream
サブクラスを作成し、操作に提供します。
プログラミング モデルの制限
上記のプログラミング モデルを任意に組み合わせることはできません。 たとえば、操作がメッセージ コントラクト型を受け入れる場合、メッセージ コントラクトはその唯一の入力パラメーターである必要があります。 さらに、操作は空のメッセージ (void の戻り値の型) または別のメッセージ コントラクトを返す必要があります。 これらのプログラミング モデルの制限については、メッセージ コントラクトの使用、メッセージ クラスの 使用、 および大規模なデータとストリーミングに関する各プログラミング モデルのトピックで説明されています。
メッセージ フォーマッタ
上記のプログラミング モデルは、 メッセージ フォーマッタ と呼ばれるコンポーネントをサービス フレームワークに接続することによってサポートされています。 メッセージ フォーマッタは、クライアントとサービス WCF クライアントでそれぞれ使用する IClientMessageFormatter または IDispatchMessageFormatter インターフェイス、またはその両方を実装する型です。
通常、メッセージ フォーマッタは動作によって接続されます。 たとえば、 DataContractSerializerOperationBehavior はデータ コントラクト メッセージ フォーマッタに接続します。 これは、サービス側で、ApplyDispatchBehavior(OperationDescription, DispatchOperation) メソッドで適切なフォーマッタにFormatterを設定するか、クライアント側で ApplyClientBehavior(OperationDescription, ClientOperation) メソッドで適切なフォーマッタにFormatterを設定することで行います。
次の表に、メッセージ フォーマッタが実装できるメソッドを示します。
インターフェイス | メソッド | アクション |
---|---|---|
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 では、パラメーターとメッセージ部分をシリアル化および逆シリアル化するための "すぐに使える" 2 つのシリアル化テクノロジ ( DataContractSerializer と XmlSerializer
) がサポートされています。 さらに、カスタム シリアライザーを記述することもできます。 ただし、WCF の他の部分 (汎用 GetBody
メソッドや SOAP エラーのシリアル化など) では、 XmlObjectSerializer サブクラス (DataContractSerializer と NetDataContractSerializerのみを使用するように制限できますが、 XmlSerializerは使用できません)、 DataContractSerializerのみを使用するようにハードコーディングすることもできます。
XmlSerializer
は、ASP.NET Web サービスで使用されるシリアル化エンジンです。
DataContractSerializer
は、新しいデータ コントラクト プログラミング モデルを理解する新しいシリアル化エンジンです。
DataContractSerializer
は既定の選択肢であり、 XmlSerializer
を使用する選択は、 DataContractFormatAttribute 属性を使用して操作ごとに行うことができます。
DataContractSerializerOperationBehavior
XmlSerializerOperationBehaviorは、DataContractSerializer
とXmlSerializer
のメッセージ フォーマッタをそれぞれ接続する操作の動作です。
DataContractSerializerOperationBehavior動作は、NetDataContractSerializer (Stand-Alone シリアル化の使用で詳しく説明) を含め、XmlObjectSerializerから派生するすべてのシリアライザーで実際に動作できます。 この動作では、 CreateSerializer
仮想メソッドのオーバーロードのいずれかを呼び出して、シリアライザーを取得します。 別のシリアライザーをプラグインするには、新しい DataContractSerializerOperationBehavior サブクラスを作成し、両方の CreateSerializer
オーバーロードをオーバーライドします。