Windows Communication Foundation アーキテクチャの概要
Microsoft Corporation
2006 年 3 月
概要 : Windows Communication Foundation (WCF) アーキテクチャとその主要な概念について紹介します。コード サンプルでは、WCF コントラクト、エンドポイント、および動作の実例を挙げます。
目次
はじめに
WCF の基礎
コード サンプル
まとめ
はじめに
この文書では、Windows Communication Foundation (WCF) アーキテクチャを紹介します。WCF の主要な概念とそれらが適合する仕組みについて説明します。概念を詳細に示すためのコード サンプルも用意していますが、この文書ではコードについては説明しません。
この文書の残りの部分は、次の 2 つの主要なセクションに分類されます:
WCF の基礎: WCF の主要な概念、用語、およびアーキテクチャのコンポーネントについて説明します。
コード サンプル: WCF の基礎で説明した概念を具体的に説明するためのコード サンプルを用意しています。
WCF の基礎
WCF サービスは、エンドポイントの集合を公開するプログラムです。各エンドポイントは世界と通信するためのポータルです。
クライアントは、1 つまたは複数のエンドポイントとメッセージを交換するプログラムです。クライアントはエンドポイントを公開して、双方向のメッセージ交換パターンでサービスからメッセージを受け取る場合もあります。
次のセクションでは、これらの 3 つの基本要素について詳細に説明します。
エンドポイント
サービス エンドポイントは、アドレス、バインディング、およびコントラクトを含みます。
エンドポイントのアドレスはネットワーク アドレスであり、エンドポイントが存在する場所を示します。EndpointAddress クラスは WCF のエンドポイント アドレスを表します。
エンドポイントのバインディングは、トランスポート プロトコル (TCP、HTTPなど)、エンコーディング (テキスト、バイナリなど)、およびセキュリティ要件 (SSL、SOAP メッセージ セキュリティなど) といった、エンドポイントが世界と通信するための方法を指定します。Binding クラスは WCF バインディングを表します。
エンドポイントのコントラクトは、エンドポイントが通信する内容を指定し、基本的には一方向、双方向、および要求/返信などの基本的な Message Exchange Pattern (MEP) で構成されるメッセージの集合です。ContractDescription クラスは WCF コントラクトを表します。
ServiceEndpoint クラスはエンドポイントを表し、EndpointAddress、バインディング、および ContractDescription が含まれており、それぞれエンドポイントのアドレス、バインディング、およびコントラクトに対応します (図 1 参照)。
図 1. 各サービスのエンドポイントは EndpointAddress 、バインディング、および ContractDescription によって表されるコントラクトを含みます。
EndpointAddress
EndpointAddress は、図 2 のように基本的には URI、ID、およびオプション ヘッダの集合です。
エンドポイントのセキュリティ ID は通常、その URI ですが、高度なシナリオでは Identity アドレス プロパティを使用して URI とは異なる ID を明示的に設定することが可能です。
オプション ヘッダは、エンドポイントの URI 以外のアドレス情報を提供するために使用します。たとえば、アドレス ヘッダは同じアドレス URI を共有する複数のエンドポイントを区別する場合に便利です。
図 2. EndpointAddress は URI を含み、 AddressProperties は ID と AddressHeaders の集合を含みます。
バインディング
バインディングは、名前、名前空間、および構成可能なバインディング要素を含みます (図 3)。バインディングの名前と名前空間はサービスのメタデータ内でバインディングを一意に識別します。各バインディング要素は、エンドポイントが世界と通信する方法を表します。
図 3. Binding クラスとそのメンバ
たとえば、図 4 は 3 つのバインディング要素を含むバインディング要素の集合を示します。各バインディング要素はエンドポイントと通信する方法を表します。TcpTransportBindingElement は、エンドポイントがトランスポート プロトコルとして TCP を使用して世界と通信することを示します。ReliableSessionBindingElement は、エンドポイントが信頼性の高いメッセージングを使用してメッセージ送信を保証することを示します。SecurityBindingElement は、エンドポイントが SOAP メッセージ セキュリティを使用することを示します。各バインディング要素は通常、エンドポイントとの通信方法を詳細に表したプロパティを収めます。たとえば、ReliableSessionBindingElement は、なし、最低 1 回、最大 1 回、1 回限りなどの必要なメッセージ送信保証を指定する Assurances プロパティを収めます。
図 4. 3 つのバインディング要素を持つバインディングの例
バインディング内のバインディング要素の順序とタイプは重要です。バインディング要素集合内のバインディング要素の順序に従って、通信スタックが構築されます。最後に集合に追加されるバインディング要素は、通信スタックの最下部コンポーネントとなり、最初に追加されるコンポーネントは最上部コンポーネントになります。受信メッセージはスタックの最下部から上に流れ、送信メッセージは最上部から下に流れます。したがって、集合内のバインディング要素の順序は、通信スタック コンポーネントがメッセージを処理する順序に直接影響します。WCF では、定義済みのバインディング セットが用意されており、カスタム バインディングを定義しなくても、ほとんどのシナリオでこれを使用することができます。
コントラクト
WCF コントラクトはエンドポイントが外側の世界と通信する内容を指定した操作の集合です。各操作は、一方向または要求/返信のメッセージ交換といった単純なメッセージ交換です。
ContractDescription クラスは WCF コントラクトとその操作を記述するために使用します。ContractDescription 内では、各 コントラクト 操作は対応した OperationDescription を含み、ここに一方向または要求/返信といった操作を記述します。各 OperationDescription は、MessageDescriptions の集合を使用して操作を構成するメッセージを表します。
ContractDescription は、多くの場合、WCF プログラミング モデルを使用してコントラクトを定義するインターフェイスまたはクラスから作成されます。このタイプには ServiceContractAttribute によって注釈が付加され、エンドポイント操作に対応するそのメソッドには OperationContractAttribute によって注釈が付加されます。属性によって注釈が付加される CLR タイプ を開始せずに、手動で ContractDescription を構築することも可能です。
双方向のコントラクトは、2 つの論理操作セットを定義します。1 つはサービスがクライアントによる呼び出しを公開するセットで、もう 1 つはクライアントがサービスによる呼び出しを公開するセットです。双方向コントラクトを定義するためのプログラミング モデルは、各セットを個別のタイプに分割し (各タイプはクラスまたはインターフェイスであることが必要です)、サービスの操作を表すコントラクトに ServiceContractAttribute によって注釈を付加して、クライアント (またはコールバック) 操作を定義するコントラクトを参照します。さらに、ContractDescription は各タイプへの参照を含み、結果として、これらを 1 つの双方向コントラクトに分類します。
バインディングと同様に、各コントラクトは名前と名前空間を持ち、サービスのメタデータ内でコントラクトを一意に識別できます。
また、各コントラクトは、コントラクトの動作を修正または拡張する ContractBehaviors の集合を含みます。次のセクションでは動作を詳細に説明します。
図 5. ContractDescription クラスは WCF のコントラクトを表します。
動作
動作はサービスまたはクライアントの機能を修正または拡張するタイプです。たとえば、ServiceMetadataBehavior が実装したメタデータの動作は、サービスがメタデータを発行するかどうかを制御します。同様に、セキュリティ動作はなりすましや承認を規制し、トランザクション動作はトランザクションの投入と自動完了を制御します。
動作はチャネルの構築プロセスに参加して、ユーザーが指定した設定に基づくチャネルや、サービス/コントラクトの他の設定を修正することもできます。
サービス動作は IServiceBehavior を実装し、サービスに適用されるタイプです。同様に、チャネル動作は IChannelBehavior を実装し、クライアント チャネルに適用されるタイプです。
サービスとチャネルの記述
ServiceDescription クラスは、WCF サービスを表すメモリ内部の構造であり、サービスによって公開されるエンドポイント、サービスに適用される動作、およびサービスを実装するタイプ (クラス) などが含まれます (図 6 参照)。ServiceDescription はメタデータ、コード/構成、およびチャネルを作成するために使用します。
この ServiceDescription オブジェクトは手動で構築することができます。また、特定の WCF 属性によって注釈が付加されたタイプから作成することもでき、このシナリオの方が一般的です。このタイプのコードは手動で作成するか、あるいは svcutil.exe と呼ばれる WCF ツールを使用して WSDL 文書から生成することができます。
ServiceDescription オブジェクトを作成して明示的に格納できますが、多くの場合、実行するサービスの一環として暗黙的に作成されます。
図 6. ServiceDescription オブジェクト モデル
同様に、クライアント側では、ChannelDescription が特定のエンドポイントへの WCF クライアントのチャネルを表します (図 7)。ChannelDescription クラスには IchannelBehaviors の集合が含まれますが、これはチャネルに適用される動作です。また、チャネルが通信するエンドポイントを表す ServiceEndpoint も収められています。
ServiceDescription とは異なり、ChannelDescription にはチャネルの通信対象であるエンドポイントを表す ServiceEndpoint は 1 つしかありません。
図 7. ChannelDescription オブジェクト モデル
WCF ランタイム
WCF ランタイムは、メッセージの送受信を行うオブジェクト セットです。たとえば、メッセージのフォーマット、セキュリティの適用、各種トランスポート プロトコルを使用したメッセージの送受信、ならびに適切な操作への受信データのディスパッチなどは、すべて WCF ランタイムです。次のセクションでは、WCF ランタイムの主要な概念について説明します。
メッセージ
WCF メッセージは、クライアントとエンドポイント間のデータ交換単位です。メッセージは基本的に、SOAP メッセージ InfoSet のメモリ内部の表現です。メッセージはテキスト XML とは無関係です。使用するエンコーディング機構に応じて、WCF バイナリ フォーマット、テキスト XML、または他のカスタム フォーマットを使用してメッセージをシリアル化することができます。
チャネル
チャネルはエンドポイントとのメッセージ送受信のための中心的なアブストラクションです。一般的に、チャネルには 2 つのカテゴリがあります。トランスポート チャネルは TCP、UDP、MSMQ などのトランスポート プロトコルを使用して、不明なオクテット ストリームの送受信を処理します。一方、プロトコル チャネルは、メッセージを処理または修正することで、SOAPベースのプロトコルを実装します。たとえば、セキュリティ チャネルは SOAP メッセージ ヘッダを追加して処理し、場合によっては暗号化してメッセージの本文を修正します。チャネルは構成可能であり、チャネルを別のチャネルの上に順番に階層化することができます。
EndpointListener
EndpointListener は ServiceEndpoint と同等のランタイムです。ServiceEndpoint の EndpointAddress、コントラクト、バインディング (場所、内容、方法) は、それぞれ EndpointListener のリスニング アドレス、メッセージのフィルタとディスパッチ、チャネル スタックに相当します。EndpointListener には、メッセージの送受信を行うチャネル スタックが含まれます。
ServiceHost と ChannelFactory
WCF サービス ランタイムは通常、ServiceHost.Open の呼び出しによって暗黙的に作成されます。ServiceHost (図 6) はサービス タイプの上から ServiceDescription を作成し、ServiceDescription の ServiceEndpoint 集合に構成、コード、または両方で定義されたエンドポイントを格納します。次に、ServiceHost は ServiceDescription を使用して、ServiceDescription の ServiceEndpoint ごとに EndpointListener オブジェクトの形式でチャネル スタックを作成します。
図 8. ServiceHost オブジェクト モデル
同様に、クライアント側では、ChannelFactory によってクライアント ランタイムが作成されます。これは ServiceHost に相当します。
ChannelFactory は、コントラクト タイプ、バインディング、および EndpointAddress に基づいて ChannelDescription を作成します。次に、この ChannelDescription を使用してクライアントのチャネル スタックを作成します。
サービス ランタイムとは異なり、クライアント ランタイムは EndpointListeners を含みません。これはクライアントが常にサービスへの接続を実行するため、受信接続を「リッスン」する必要がないためです。
コード サンプル
このセクションでは、サービスとクライアントの構築方法を示したコード サンプルを提供します。これらのサンプルは、上述の概念を具体的に示すためのものであり、WCF プログラミングの技法を示すものではありません。
コントラクトの定義と実装
上述のとおり、コントラクトを最も簡単に定義する方法は、インターフェイスまたはクラスを作成して、ServiceContractAttribute によってこれに注釈を付加することです。これによってシステムから ContractDescription を簡単に作成できるようになります。
インターフェイスまたはクラスを使用してコントラクトを定義する場合、コントラクトのメンバであるインターフェイスまたはクラスの各メソッドには、OperationContractAttribute によって注釈を付加する必要があります。以下に例を示します。
using System.ServiceModel;
//インターフェイスによる WCF コントラクトの定義
[ServiceContract]
public interface IMath
{
[OperationContract]
int Add(int x, int y);
}
この例では、コントラクトの実装は単に IMath を実装するクラスを作成するだけです。このクラスが WCF Service クラスになります。以下に例を示します。
//サービス クラスがインターフェイスを実装
public class MathService : IMath
{
public int Add(int x, int y)
{ return x + y; }
}
エンドポイントの定義とサービスの開始
エンドポイントはコードまたは構成内で定義することができます。以下の例では、DefineEndpointImperatively メソッドにより、コードでエンドポイントを定義してサービスを開始するための最も簡単な方法を示します。
DefineEndpointInConfig メソッドは、これに相当する構成で定義されるエンドポイントを示します (構成の例はコードの次にあります)。
public class WCFServiceApp
{
public void DefineEndpointImperatively()
{
//MathService のサービス ホストを作成
ServiceHost sh = new ServiceHost(typeof(MathService));
//AddEndpoint ヘルパー メソッドを使用し
//ServiceEndpoint を作成して
//ServiceDescription に追加
sh.AddServiceEndpoint(
typeof(IMath), //コントラクト タイプ
new WSHttpBinding(), //組み込みバインディングの 1 つ
"http://localhost/MathService/Ep1"); //エンドポイントのアドレス
//サービス ランタイムの作成とオープン
sh.Open();
}
public void DefineEndpointInConfig()
{
//MathService のサービス ホストを作成
ServiceHost sh = new ServiceHost (typeof(MathService));
//サービス ランタイムの作成とオープン
sh.Open();
}
}
<!-- configuration file used by above code -->
<configuration
xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.serviceModel>
<services>
<!-- service element references the service type -->
<service type="MathService">
<!-- endpoint element defines the ABC's of the endpoint -->
<endpoint
address="http://localhost/MathService/Ep1"
binding="wsHttpBinding"
contract="IMath"/>
</service>
</services>
</system.serviceModel>
</configuration>
エンドポイントへのメッセージの送信
以下のコードは、メッセージを IMath エンドポイントに送信するための 2 つの方法を示します。SendMessageToEndpoint はチャネルの作成を暗黙的に実行しますが、SendMessageToEndpointUsingChannel の例では、これを明示的に実行しています。
SendMessageToEndpoint の最初の例では、svcutil.exe というツールとサービスのメタデータを使用して、コントラクト (この例では IMath)、コントラクトを実装するプロキシ クラス (この例では MathProxy)、および関連する構成 (この例にはなし) を生成しています。Imath によって定義されるコントラクトは内容 (実行する操作) を指定し、生成された構成にはバインディング (方法) とアドレス (場所) が含まれます。
このプロキシ クラスを使用するには、単にこれをインスタンス化して Add メソッドを呼び出します。プロキシ クラスは暗黙的にチャネルを作成し、これを使用してエンドポイントと通信します。
下記の SendMessageToEndpointsUsingChannel の 2 番目の例は、ChannelFactory を直接使用したエンドポイントとの通信を示します。この例では、プロキシ クラスと構成ではなく、ChannelFactory<IMath>.CreateChannel を直接使用してチャネルを作成しています。また、構成を使用してエンドポイントのアドレスとバインディングを定義する代わりに、ChannelFactory<IMath> コンストラクタによってこれらの 2 つの情報をパラメータとして取得しています。エンドポイントの定義が必要な 3 番目の情報、すなわちコントラクトはタイプ T として受け渡されます。
using System.ServiceModel;
//svcutil.exe によってコントラクトを
// サービスのメタデータから生成
public interface IMath
{
[OperationContract]
public int Add(int x, int y)
{ return x + y; }
}
//svcutil.exe によってクラスを
//サービスのメタデータから生成
//生成される構成の記述なし
public class MathProxy : IMath
{
...
}
public class WCFClientApp
{
public void SendMessageToEndpoint()
{
//svcutil.exe によってサービスのメタデータから
//作成されたプロキシ クラスを使用
MathProxy proxy = new MathProxy();
int result = proxy.Add(35, 7);
}
public void SendMessageToEndpointUsingChannel()
{
//ChannelFactory を使用してチャネルを作成
//アドレス、バインディング、コントラクト タイプ (IMath) の
//指定が必要
ChannelFactory<IMath> factory=new ChannelFactory<IMath>(
new WSHttpBinding(),
new EndpointAddress("http://localhost/MathService/Ep1"));
IMath channel=factory.CreateChannel();
int result=channel.Add(35,7);
factory.Close();
}
}
カスタム動作の定義
カスタム動作を定義するには、IServiceBehavior (クライアント側動作では IchannelBehavior) を実装します。次のコードは、IserviceBehavior を実装する動作の例です。IServiceBehavior.ApplyBehavior では、コードによって ServiceDescription が検証されて、各 ServiceEndpoint のアドレス、バインディング、コントラクト、および ServiceDescription の各動作の名前が書き出されます。
この特別な動作は属性でもあるため (System.Attributeを継承)、下記のように宣言によって適用することが可能です。ただし、動作には属性は不要です。
[AttributeUsageAttribute(
AttributeTargets.Class,
AllowMultiple=false,
Inherited=false)]
public class InspectorBehavior : System.Attribute,
System.ServiceModel.IServiceBehavior
{
public void ApplyBehavior(
ServiceDescription description,
Collection<DispatchBehavior> behaviors)
{
Console.WriteLine("-------- Endpoints ---------");
foreach (ServiceEndpoint endpoint in description.Endpoints)
{
Console.WriteLine("--> Endpoint");
Console.WriteLine("Endpoint Address: {0}",
endpoint.Address);
Console.WriteLine("Endpoint Binding: {0}",
endpoint.Binding.GetType().Name);
Console.WriteLine("Endpoint Contract: {0}",
endpoint.Contract.ContractType.Name);
Console.WriteLine();
}
Console.WriteLine("-------- Service Behaviors --------");
foreach (IServiceBehavior behavior in description.Behaviors)
{
Console.WriteLine("--> Behavior");
Console.WriteLine("Behavior: {0}", behavior.GetType().Name);
Console.WriteLine();
}
}
}
カスタム動作の適用
すべての動作を命令によって適用するには、動作のインスタンスを ServiceDescription (クライアント側では ChannelDescription) に追加します。たとえば、InspectorBehavior を命令によって適用する場合、次のように記述します。
ServiceHost sh = new ServiceHost(typeof(MathService));
sh.AddServiceEndpoint(
typeof(IMath),
new WSHttpBinding(),
"http://localhost/MathService/Ep1");
//動作を命令によって追加
InspectorBehavior behavior = new InspectorBehavior();
sh.Description.Behaviors.Add(behavior);
sh.Open();
さらに、System.Attribute を継承する動作は、宣言によってサービスに適用することもできます。たとえば、InspectorBehavior は System.Attribute を継承するため、次のように宣言によって適用できます。
[InspectorBehavior]
public class MathService : IMath
{
public int Add(int x, int y)
{ return x + y; }
}
まとめ
WCF サービスはエンドポイントの集合であり、各エンドポイントは世界との通信ポータルです。各エンドポイントはアドレス (Address)、バインディング (Binding)、およびコントラクト (Contract) を収めます (ABC)。アドレスはエンドポイントが存在する場所、バインディングはエンドポイントが通信する方法、およびコントラクトはエンドポイントが通信する内容を示します。
サービス側では、ServiceDescription が ServiceEndpoints の集合を収めており、各 ServiceEndpoints はサービスが公開するエンドポイントを記述します。この記述から、ServiceHost は、ServiceDescription の ServiceEndpoint ごとに EndpointListener を収めたランタイムを作成します。エンドポイントのアドレス、バインディング、コントラクト (場所、方法、内容) は、それぞれ EndpointListener のリスニング アドレス、チャネル スタック、メッセージのフィルタとディスパッチに相当します。
同様に、クライアント側では、ChannelDescription がクライアントの通信対象である 1 つの ServiceEndpoint を収めます。この ChannelDescription から、ChannelFactory がサービスのエンドポイントと通信できるチャネル スタックを作成します。