このトピックはまだ評価されていません - このトピックを評価する

Windows Communication Foundation サービスの構築

Clemens Vasters

newtelligence AG

September 2005

日本語版最終更新日 2006 年 6 月 8 日

概要 : Windows の新しいシステム プラットフォームである Windows Communication Foundation (コードネーム "Indigo") の基本的な概念を紹介します。ここでは、Windows Communication Foundation の System.ServiceModel 名前空間を使用して、サービスおよびサービス クライアントを構築する方法について説明します。

   この文書とコード サンプルは "WinFX Beta2 Febrary CTP" に対応するものです。今後の Windows Communication Foundation のリリース時には変更される可能性があります。
目次

Windows Communication Foundation とは Windows Communication Foundation とは
Windows Communication Foundation の ABC Windows Communication Foundation の ABC
サービス コントラクトの定義 サービス コントラクトの定義
データ コントラクトの定義 データ コントラクトの定義
RPC と メッセージング RPC と メッセージング
サービスの実装 サービスの実装
サービスのホスティング サービスのホスティング
バインディングの選択と構成 バインディングの選択と構成
バインディングの要件と検証 バインディングの要件と検証
クライアント クライアント
まとめ まとめ

技術記事の執筆家が、既存のテクノロジに関する機能拡張や別の視点からの考察記事ではなく、まったく新しいテクノロジを紹介できる機会はほどんどありません。今回は、その希少な機会を得ることができました。ここでは、"Hello World" を表示するプログラムの作成から始めるのではなく、子供向け英語番組 "セサミストリート" での "ABC" のように、Windows Communication Foundation (WCF) の基本的な世界の探検から始めましょう。その前に、Windows Communication Foundation の概要と、マイクロソフトがこれを構築した理由について説明します。

Windows Communication Foundation とは

Windows Communication Foundation (WCF) とは何でしょうか。WCF とは、マイクロソフトの次世代プログラミング プラットフォームおよびランタイム システムであり、ネットワーク分散サービスを構築、構成、および展開することができます。WCF ランタイムと、その主要なプログラミング インターフェイスである System.ServiceModel 名前空間は、開発者が過去 10 年以上にわたって Windows プラットフォーム上での分散アプリケーションの構築に使用してきた分散システム テクノロジを統合した次世代のテクノロジです。

最初に、多くの優れたアプリケーションの基盤として現在使用されている分散システム テクノロジについて解説します。これらのテクノロジには、ASP.NET Web Services (ASMX) を筆頭に、Web Service Enhancements (WSE) 拡張、Microsoft メッセージ キュー (MSMQ)、Enterprise Services/COM+ ランタイム環境、および .NET Remoting があります。マイクロソフトではこれらのテクノロジをすべて置き換えようとしていますが、何か問題点があるのでしょうか。それはテクノロジの選択肢が多すぎることです。

ASMX と WSE を使用すると、Web サービスおよび Web サービス クライアントと相互運用ができる、強力なサービス指向アプリケーションを構築できるため、クロスプラットフォームの相互運用と統合、および疎結合による比較的容易なサービスの展開とバージョニング戦略を実現できます。キューイングされたメッセージは、スケーラブルで、長期にわたって利用され、また変化しやすいメッセージです。このため、データの移行は確実に行う必要があります。MSMQ を使用すると、このようなメッセージ用の強力なメカニズムを装備できます。Enterprise Services は、分散環境で関連処理を実行する複数の組織間をトランザクションで統合し、共有リソースへ制限をかけたアクセスを可能にします。また、オブジェクト インスタンスをプールして、初期化コストのかかるリソースへのアクセスを最適化することもできます。さらに、イベントのパブリッシュ/サブスクライブ メカニズムと豊富なセキュリティ モデルを備えており、プラットフォームに統合された、実績のある安全かつ高速なトランスポートが実現されます。最後になりますが、Remoting も重要なテクノロジの一つです。Remoting は 共通言語ランタイム (CLR) 統合メカニズムをサポートしており、アプリケーション ドメインの境界を越えてオブジェクトと通信することができます。また、開発者に定評の高い、柔軟で拡張性の高いモデルを備えており、トランスポート、プロキシ メカニズム、および通信チャネルの動作方法を拡張できます。

これらの基盤となるテクノロジ自体には、問題はありません。ただし、どのテクノロジを使用するのかを、排他的かつ明示的に選択する必要がある、ということが問題になります。Enterprise Services の機能を使用する場合の実装方式は、ASMX を使用する場合とは大きく異なります。プレーンな HTTP 経由でのメッセージ送信の危険をおかさずに、堅牢で信頼性の高い MSMQ 経由でメッセージ送信を必要とする場合、プラミング コード (plumbing code) を自分で追加するか、あるいは Enterprise Service のキュー コンポーネント機能 (元の話に戻る) を使用しない限り、実装するコードは、メソッド指向なプロキシのメソッドを呼び出すものとは大きく異なります。

WCF の目的は、このようなテクノロジの事前選択の必要性をなくすことと、1 つのテクノロジ プラットフォームに関する要件を円滑に組み合わせて実装できるようにすることです。セッションとトランザクション フローをサポートする信頼性の高い通信を使用して Web サービスを構築したり、システム内を流れる生のメッセージを検査するために拡張するような場合に、WCF は大きな威力を発揮します。現在、これを実現することは (マイクロソフト以外のプラットフォームを含めて)、完全に不可能というわけではありません。ただし、上記のすべてのテクノロジに関して、開発者が多くのシステムレベルの背景知識を習得することが必要となり、また膨大な時間も必要となります。

WCF を使用した場合、開発者は ASMX、WSE、Enterprise Services、MSMQ、および Remoting が持つ機能を統合した単一のプログラミング モデルを習得するだけでよいため、開発者の生産性の向上が期待できます。WCF は、マイクロソフトが過去 5 年間に業界のパートナーと協力して開発した、上記のすべての WS-* 標準を実装したものです。したがって、プラットフォーム、ランタイム、およびプログラミング言語間で広範な相互運用性を実現できます。

それでは、この WCF について解説しましょう。

Windows Communication Foundation の ABC

"ABC" とは WCF の大切な概念を示す言葉です。"ABC" は、WCF サービスのエンドポイントの構成を理解する上で重要です。セサミストリートに出てくるキャラクターのアーニー、バート、クッキー モンスター、ビッグ バードたちも、"ABC" から教えてくれます。"ABC" は次のことを意味します。

  • "A" は アドレス (Address) を表します。サービスの場所です。

  • "B" は バインディング (Binding) を表します。サービスとの対話方法です。

  • "C" は コントラクト (Contract) を表します。提供されるサービスの内容です。

朝食の食卓で Web Services Description Language (WSDL) の記述を読むほどの熱心な Web サービス ユーザーであれば、これらの 3 つの概念は WSDL で表現される 3 つの抽象レベルであることが容易に理解できるでしょう。このような、山括弧であふれた WSDL の世界で生活しているユーザーであれば、次のように理解することもできます。

  • "A" は アドレス (Address) を表します。wsdl:service セクションを例とした場合、wsdl:binding を具体的なサービスのエンドポイント アドレスにリンクすることです。

  • "B" は バインディング (Binding) を表します。wsdl:binding セクションを例とした場合、wsdl:portType コントラクトを具体的なトランスポート、エンベロープの形式、および関連ポリシーにバインドすることです。

  • "C" は コントラクト (Contract) を表します。wsdl:portType、wsdl:message、および wsdl:type セクションを例とした場合、タイプ、メッセージ交換パターン、および操作を記述することです。

"ABC" は、WCF サービスの記述 (および構成) には必ず次の 3 段階のプロセスがあることを表しています。

  • コントラクトを定義してサービス上で実装します。

  • トランスポートに加え、サービス品質、セキュリティ、およびその他のオプションを指定するサービスのバインディングを選択または定義します。

  • コントラクトのエンドポイントを、ネットワーク アドレスに紐付けて (バインディング定義を使用、すなわち名前によって) 展開します。

これらの 3 つの要素が独立していることは重要です。コントラクトは多数のバインディングをサポートでき、バインディングは多数のコントラクトをサポートできます。サービスは、多数の (アドレスに紐付いたコントラクトの) エンドポイントを共存させ、同時に有効化することができます。したがって、HTTP 経由で SOAP 1.1 を使用した最大限の相互運用性を実現するサービスを公開し、さらに TCP 経由でバイナリ ワイヤ エンコーディングを使用した最大限のパフォーマンスを実現するサービスを公開する場合には、2 つのエンドポイントは、まったく同じサービス上に共存することができます。

当然ですが、すべてのバインディングが所定のサービス コントラクトまたはサービス実装のニーズを満たすわけではありません。たとえばサービスが、特定のセキュリティ アスペクト、信頼性の高いメッセージング、トランザクション フロー、もしくは他の機能に依存している場合には、エンドポイントを公開するバインディングが、このような依存の対象となる機能をサポートしている必要があります。必要な機能をバインディングがサポートしていない場合、WCF ランタイムは機能の不適合を検出し、サービスの開始を拒否します。

サービス コントラクトの定義

WCF コントラクトを定義するには、2 つの方法があります。1 つは、WSDL コントラクトを設計または入手し、WCF ランタイム バイナリに同梱されている svcutil.exe ツールを使用して、これを WCF コントラクトに変換する方法です。"WCF コントラクト" に基づいて、ツールはメタデータ属性でマークアップされたコードを生成します。このメタデータ属性は、コントラクト定義の詳細を WCF ランタイムに通知するためのものです。ASMX では、wsdl.exe ツールは具象的なプロキシ クラスの実装、もしくは具象的 (または抽象的) なサーバー クラスの実装のどちらか一方のために WSDL をインポートしますが、WCF ツールはサーバー側とプロキシ側の両方に使用できるインターフェイス宣言としてコントラクトを生成します。マイクロソフトの TerraService のコントラクトをインポートする場合、コマンドラインは次のようになります。

Svcutil.exe http://terraserver.microsoft.com/TerraService2.asmx /useXmlSerializer

生成される出力は、必要なすべてのデータ宣言、サービス インターフェイス、および実装可能な状態になっているプロキシ クラスが格納されたソースコード ファイルです (言語スイッチを使用してプログラミング言語を指定できます)。

さらに一般的かつ簡単な方法で WCF サービス コントラクトを定義することもできます。それは、WSDL ではなく、お好みのプログラミング言語でコントラクト宣言を記述する方法です。とどのつまり、WSDL および他のすべての WS-* 仕様の複雑な詳細をすべて処理し、サービスを相互運用可能にするのは、プラットフォームの仕事であるべきです。WSDL でコントラクト宣言を記述するのか、あるいは適切にマークアップされた Visual Basic や C# のインターフェイスを宣言すべきかどうかは、実際には重要ではありません。C# で定義して WSDL にエクスポートする場合も、WSDL で定義して Visual Basic にインポートする場合も、生成されるメタデータに相違はありません。WCF が原因で、このような同等のエクスポートやインポートが実行できない場合は、警告を受けます。WCF から見ると、WSDL または CLR インターフェイス宣言は異なるものですが、いずれもまったく同じメタデータを表現するための有効な手段です。ただし、Visual Basic や C# は、WSDL による宣言よりもプログラマには使いやすい方法です。大多数のユーザーは、そのように感じると思われます。ここでは、WSDL は省略し、コードに注目していきます。

public interface ICalculate
{
   double Add( double a, double b);
   double Subtract( double a, double b);
}

上記のコードは、C# で記述された非常に単純な CLR インターフェイス宣言です。Remoting または Enterprise Services を使用している場合、このようなインターフェイスとすべてのメソッドは本質的に "リモート可能 (remotable)" になりますが、WCF ではそのようなことはありません。

WCF は、"明示的な境界" というサービス指向の概念に従っています。つまり、明示的にランタイムに指示しない限りは、WCF からは何も公開されません。WCF は、アプリケーション専用の要素と、可視化して外部にアクセスできる要素とを厳格に区別します。"public"、"private"、"protected" という C# や Visual Basic のキーワード、および基礎となるアクセス保護メカニズム (セキュリティではありません) は、アプリケーション内のコードにとっては重要な意味がありますが、アプリケーションの範囲内で公開されるインターフェイスやメソッドを実際に外部に公開するかどうかは、これらのキーワードや保護メカニズムとは切り離して考慮すべき問題です。また、外部からアプリケーションへのアクセス専用に設計されたメソッドが、アプリケーション内で誤って呼び出されないようにすることが重要になる場合もあります。このような場合、メソッドはアプリケーションの内部に対しては "private" として宣言されますが、実際には外部からのアクセスが可能です。

using System.ServiceModel;
[ServiceContract]
public interface ICalculate
{
   [OperationContract]
   double Add( double a, double b);
   [OperationContract]
   double Subtract( double a, double b);
}

ここでは、インターフェイスに、[ServiceContract] 属性のラベルが付いています。この属性は、WCF のコントラクト メタデータであることを伝えるために、明示的に CLR インターフェイスをマークします。各メソッドには [OperationContract] 属性のラベルが明示的に付いています。これは "public" に相当する WCF メソッドです。これらのメタデータ属性に指定できる、既定のオプション パラメータや追加のオプション パラメータに基づいて、WCF ランタイムは必要な場合にいつでも、[ServiceContract] を同等の WSDL portType 宣言に変換できます。次に、各 [OperationContract] 宣言は、対応する WSDL operation 宣言にマッピングされ、パラメータ リストと戻り値は、WSDL types セクションと各 WSDL message マッピングによって参照される XML スキーマ宣言に変換されます。この記事の後半でコントラクトについて詳しく解説するときに、もう一度これらの属性を説明します。

[ServiceContract]
public class Calculator
{
   [OperationContract]
   public double Add(double a, double b) { ... };
   [OperationContract]
   private double Subtract(double a, double b) { ... };
}

上記のコードは非常に特殊なコントラクトです。このクラスは、インターフェイスを明示的に実装しませんが、コントラクトが "インライン (inline)" であることを宣言するサービス実装です。これは特殊な宣言形式 (必ずしも推奨するわけではありません) であり、既存のコードを、コードのリファクタリングや既存のクラスからのインターフェイス抽出を行わずに、WCF へ移行することができます。それどころか、サービスとして公開したいすべてのメソッドを、適切な属性でマークアップすることができます。この宣言形式の使用候補となるのは、ほとんどインターフェイスを実装していない、既存の ASMX Web サービス、Remoting クラス、または Enterprise Services のサービス コンポーネントの移行です (ただし、ベスト プラクティスのガイドラインの推奨方法とは異なります)。C# で 1 つのメソッドが private として宣言されていますが、それでも[OperationContract] 属性は有効です。したがって、このメソッドは、サービス アプリケーションの内部ではなく、外部から (あるいはサービス クラス自身の内部から) しか呼び出すことはできません。サービスの実装では、明示的なインターフェイスと暗黙的なインターフェイスを組み合わせることはできません。2 つの実装形式のいずれかを選択する必要があります。

データ コントラクトの定義

上記のサンプル コードで示したコントラクトの操作 (operation) では、単純な型のパラメータを受け取り、また戻り値も単純な型になっています。このような単純な型のパラメータ リストを扱うとき、多くの場合、サービス間で交換されるデータがメモリ内部のオブジェクト表現から XML 情報セットにシリアライズされ、ワイヤ転送用にエンコードされる (逆もまた然り) という事実は気にされません。

ただし、事前に定義した WSDL ドキュメントを扱う場合や、複合型の引数を使用する場合は、"データ コントラクト (Data Contract)" について考慮する必要があります。サービス コントラクトがインターフェイスの形態とルール (portTypes)、および関連するメッセージと操作を定義するのに対して、データ コントラクトは操作の入出力メッセージを介して交換されるデータの形態とルールを定義します。

データ   コントラクトサービス   コントラクトが分離していることは重要です。一般的にサービス コントラクトは、論理的かつ意味的な関連を持つ操作のセットを、インターフェイスににおけるグループとして定義し、サービスの振る舞いを表します。データ コントラクトは、サービスの境界を越えて移動し、プロバイダ側とコンシューマ側のロジックで処理される情報項目を定義します。英文法の観点で見ると、操作は述語に、流れるデータは目的語に相当します。これに対して、呼び出し元は主語に相当します。主語は目的語を必要とします。また、あらゆる "述語" が操作になります。

[DataContract]
public class Person
{
   [DataMember]
   public int Id;
   [DataMember]
   public string FirstName;
   [DataMember]
   public string LastName;
}

上記のコードは、人 (Person) に関するデータ コントラクトです。属性は、新しい DataContractSerializer インフラストラクチャの基幹となる System.Runtime.Serialization 名前空間から提供されています。DataContractSerializer は、よく知られた XmlSerializer (Binary FormatterSoapFormatter を含む) と目的が似ていますが、動作は大きく異なります。DataContractSerializer を詳細に説明すると、この記事の範囲を越えてしまうため、この新しいシリアライズ インフラストラクチャと .NET Framework に既存のインフラストラクチャの主要な違いに焦点を当てます。

DataContractSerializerXmlSerializer との最も顕著で重要な違いは、新しいインフラストラクチャでは、XML スキーマや、データのシリアライズとデシリアライズの方法に対して、開発者が制御できる要素が意図的に少なくなっているという点です。一般的に、新しいソフトウェアでは制御性と柔軟性を向上させることが多いため、このような制御性の減少は不思議に思われるかもしれませんが、実際には、この相違点によって得るメリットがあります。それは、メッセージの自動バージョニング サポートについて、詳細な制御が可能になるという点です。つまり、疎結合で進化可能なデータ構造を容易に実装できるため、異なるデータ コントラクト バージョン (スキーマ バージョン) 間でのアップグレードやダウングレードが可能になります。この新しい機能を利用するには、スキーマの詳細部分に対する制御のいくつかを、シリアライザ インフラストラクチャに委ねる必要があります。DataContractSerializer を使用すると、例えばデータ コントラクトの一部を XML の属性、他の部分を XML の要素として宣言することはできません。すべてのデータは、常に XML の要素としてシリアライズされます。

[DataContract(Name="Person")]
public class Person2
{
   [DataMember]
   public int Id;
   [DataMember]
   public string FirstName;
   [DataMember]
   public string LastName;
   [DataMember(IsRequired = false)]
   public string MiddleName;
}

上記のコードは、前のサンプルを少し変更したもので、新しいメンバが追加されています。[DataMember] 属性セットの IsRequired プロパティを持つ新しいメンバに false (既定値は false) のラベルを付加することで、DataContractSerializer インフラストラクチャに対して、新しく導入されるメンバをオプションのメンバとして処理するように命令します (なお上記の例では、メンバ追加以前のメンバもすべてオプションのメンバとして処理されることになります)。以前のバージョンのコントラクトによってシリアライズされたデータを受け取る場合 (クライアント プロキシに埋め込まれている可能性があります)、この新しいメンバはデシリアライズ時には既定値を保持しているため、新しいバージョンは、このメンバを持たない以前のバージョンの入力を問題なく受け取ることができます。IsRequiredtrue が設定されており、デシリアライズ時にその要素が欠落している場合は、例外が発生します。

RPC と メッセージング

WCF はサービス指向を原則として構築されます。サービス指向の考え方の 1 つに、サービスはプロシージャ コールではなく、メッセージを交換するという考え方があります。ただし、上記のサービス コントラクト定義は、COM、Remoting、または Enterprise Services の RPC 形式の定義と異なるようには見えないため、この考え方に矛盾するように思われます。

このような表面上の類似性は問題ではありません。"従来の" コンポーネントと外観や動作が類似するサービスを構築したい場合にはそのようにでき、WCF がメソッドの呼び出しをバックグラウンドでメッセージに変換する仕組みについて考慮する必要はありません。また、WCF サービスはセッションをサポートし、サービス インスタンスの有効期間を詳細に制御できます。WCF で構築されるサービスは、ASMX のような、特定のアーキテクチャ形式に基づく "ステートレスな" サービスに限定されません。呼び出し毎、または定義した呼び出しシーケンス毎にインスタンスの生成や破棄を行うサービスを構築したり、セッションの期間中にインスタンスが存在するサービスを構築したりすることができます。また、1 つのサービス インスタンスがすべての呼び出し元で共有されるシングルトン サービスも構築できます。

DCOM や Remoting などの RPC テクノロジとは大きく異なり、WCF では、参照を使用してオブジェクトを渡すことや、コールバックのデリゲートを渡すことができません。この制限の理由は、サービス指向のテクノロジでは、ローカル エリア ネットワーク テクノロジである DCOM または Remoting のように、ネットワークに関するさまざまな前提条件を設定できないためです。コールバックとオブジェクト参照では、サーバーからクライアントへのコールバック パスが必要です。このパスによって、必要なときにサーバーとクライアントとのやり取りが行われます。プラットフォーム、信頼境界、およびワイド エリア ネットワークにサービスが分散していることを前提とする環境では、多くの場合、このような双方向接続は存在しません。クライアントはファイアウォール、または NAT (ネットワーク アドレス変換) サービスの背後に常駐するか、あるいはメッセージをアクティブにリッスンしません。クライアントがメッセージをリッスンしても、セキュリティ制限により、応答を含む送信元の操作はエンドポイントで認証を受ける必要があります。したがって、コールバックやオブジェクト参照によって確立されるような暗黙的なバックチャネルは、サービスの世界では機能しません。このような機能を使用する場合は、いわゆる "全二重 (duplex)" の対話を使用して、バックチャネルを明示的に確立する必要があります。このトピックについては、専門記事を参照してください。

RPC 形式ではなく "メッセージング形式" のプログラミングを使用する場合、通常の入力メッセージを使用するか、"生の" WCF Message インスタンスを送信するか、あるいは操作のパラメータ リストを使用してメッセージの内容を構成することができます。

public enum UpdateConcurrencyMode
{
UpdateIfModified,
FailIfModified
}
[DataContract]
public class UpdateBehavior
{
   [DataMember]
   public UpdateConcurrencyMode UpdateConcurrencyMode;
}
[MessageContract]
public class StorePersonMessage
{
   [MessageHeader]
   public UpdateBehavior UpdateBehavior;
   [MessageBody]
   public Person Person;
}
[ServiceContract]
public interface IPeople
{
   [OperationContract]
   public void StorePerson(StorePersonMessage storePersonMessage);
}

"型付きメッセージ (typed message)" は明示的に構成されたメッセージ構造で、上記のコードのように [MessageContract] 属性がラベル付けされ、0 個以上の [MessageHeader] メンバと [MessageBody] メンバが含まれます。属性の名前に従って、[MessageHeader] メンバは SOAP エンベロープ内のヘッダーにマッピングされ、[MessageBody] メンバは SOAP エンベロープのボディ セクション内の要素として表現されます。

[ServiceContract]
public interface IPeople
{
   [OperationContract]
   public void StorePerson(
[MessageHeader] UpdateBehavior UpdateBehavior,
[MessageBody] Person Person);
}

SOAP エンベロープのヘッダー セクション内の情報を送信し、明示的なメッセージ クラスを定義しない場合は、操作のパラメータ リストにメッセージ コントラクトを "インライン" で定義できます。上記のコードで [MessageHeader] 属性がラベル付けされたパラメータは、エンベロープのヘッダー セクションにマッピングされ、他のすべての引数はエンベロープのボディになります。ボディのすべて、または一部の内容をフリーな形式の XML にする場合、XmlNode または XmlElement 型のパラメータを使用できます。これはインラインのメッセージ コントラクトおよび明示的なメッセージ コントラクトのメンバに対して有効です。

[ServiceContract]
public interface IPeople
{
[OperationContract]
public void StorePerson(Message msg);
}

メッセージの内容を完全に制御する必要がある場合は、単一の System.ServiceModel.Channels.Message 引数を操作で使用することもできます。これにより WCF Message に即座にアクセスできます。このクラスはすべてのヘッダーをコレクションに公開するため、セキュリティとアドレス指定に関連する特別なヘッダーの内容は、明示的なパラメータによって公開されます。本文の内容は XmlDictionaryReader として表現されます。

[ServiceContract]
public interface IMyMessageHandler
{
   [OperationContract(Action="*")]
   public void HandleAnyMessage(Message message);
}

上記の例では、WCF は依然としてメッセージを適切な操作にディスパッチします。コントラクトの [OperationContract] の Action プロパティ値 (既定では操作のメソッド名) のいずれかに対応する Action ヘッダーを持たないメッセージは拒否されます。Action ヘッダーに関係なくすべてのメッセージを受け取る場合は、Action プロパティにワイルドカードのインジケータ (単一のアスタリスク ("*")) を設定した操作を追加できます。ワイルドカードが設定された操作が存在する場合、ディスパッチできないすべてのメッセージは、この操作にディスパッチされます。当然ですが、これをサービス コントラクトの唯一の操作にすることも可能です。これは基本的にフリーな形式の SOAP メッセージ エンドポイントになります。

コントラクトを調整し、メタデータ マークアップとパラメータの型を組み合わせることで、純粋な RPC 形式のインターフェイスや生の SOAP メッセージなど、あらゆるものを作成でき、しかも同一の基本的なプログラミング モデルと大きく異なる点がないということは、WCF の非常に優れた特徴であるといえます。

サービスの実装

サービスを実装するには、1 つまたは複数の定義済みのサービス コントラクト インターフェイスを選択し、目的のプログラミング言語を使用して、このインターフェイスを通常の CLR クラスに実装します。Enterprise Services や Remoting とは異なり、サービス クラスを特別な基底クラスから派生させる必要はありません。前にも説明しましたが、特別な例は "インライン" としてクラス メソッド上に直ちに定義されるコントラクトを含んだクラスですが、これらのクラスでも、特別な基底クラスは必要ありません。

[ServiceContract]
public interface IPeople
{
   [OperationContract()]
   [TransactionFlow( TransactionFlowOption.Allowed )]
   public void StorePerson(UpdateBehavior updateBehavior, Person person);
}
public class People : IPeople
{
   public void StorePerson(UpdateBehavior updateBehavior, Person person)
   { ... }
}

多くの場合、とりわけメッセージを処理しないときは、サービスの実装そのものは特別なものではありません。必要なロジックを実装するだけで十分です。

[ServiceBehavior(ValidateMustUnderstand = true)]
public class People : IPeople
{
   [OperationBehavior(
TransactionScopeRequired = true,
TransactionAutoComplete = true,
ReleaseInstanceMode=ReleaseInstanceMode.BeforeAndAfterCall)]
   public void StorePerson(UpdateBehavior updateBehavior, Person person)
   { ...   }
}

しかしながら、WCF ランタイムを使用して、サービスに対して特定のビヘイビア (behavior : 振る舞い) を実行時に追加するといった特殊な操作を行うことは可能です。Enterprise Services でも同じようなビヘイビアがあります。たとえば、操作の自動トランザクション スコープが必要な場合、あるいは WCF でサービス インスタンスが複数の同時スレッドからどのような理由であっても呼び出されないようにする場合は、[ServiceBehavior] および [OperationBehavior] 属性を実装に適用できます。これにより、WCF は要求されたビヘイビアをサービス インスタンスの "周囲" に自動的に実装します。これは Enterprise Services (または COM+) が ServicedComponent (または 構成済みコンポーネント) に対して行う処理に似ています。

WCF で簡単に使用できるビヘイビア (Enterprise Services とは異なり、開発者は独自のビヘイビアを追加できますが、詳細についてはこの記事では割愛します) は、Enterprise Services/COM+ で使用可能なビヘイビアと似ていますが、柔軟性と制御性が大幅に向上しています。この良い例が自動トランザクションです。

Enterprise Services ではクラスレベルで自動トランザクションのサポートを定義しますが、WCF では操作単位で動作を定義できます。したがって、トランザクションおよび非トランザクションの両方の操作を同じサービス実装で装備できます。他の異なる点として、WCF は、トランザクション スコープが自動的に作成されても、既存のトランザクション スコープが操作に使用されても、ある操作から次の操作への "トランザクション フロー" を分離します。呼び出し元から外部トランザクションを受け取る (すなわち外部からのトランザクション フローに参加する) 機能は、[TransactionFlow] 属性のコンストラクタ パラメータで示されます。呼び出し元がローカル トランザクションを持ち、トランザクションをリモート サービスに送信する場合、起動された操作のトランザクション フローを明示的に許可するコントラクトのみがこれを実行します。

このようなリモートで提供されるトランザクションにサービスを自動的に組み込むかどうかは、トランザクション フローとは別に考慮する必要があります。コントラクトがトランザクション フローを許可しても、そのコントラクトの特定の実装が、実際には自動トランザクションを使用しない場合があります。このように実装によって異なるため、操作をトランザクションに自動的に組み込むかどうかは、[OperationBehavior] 属性の TransactionScopeRequired プロパティによって制御されます。

このプロパティに加えて、TransactionAutoComplete プロパティがあります。これは例外が発生せずに操作が戻されたときにサービスがトランザクションを正常終了 (コミット) と自動的にマークするかどうか、あるいは例外が発生した場合に異常終了 (アボート) としてマークするかどうかを制御します。このプロパティは Enterprise Services の [AutoComplete] 属性によく似ていますが、ビヘイビアがすべて同じわけではありません。Enterprise Services では、"自動完了" のメソッドはトランザクションの投票を制御するだけでなく、コンポーネント インスタンスも破棄されます。WCF では、インスタンスのビヘイビアは個別に定義されます。一般的に、適切なトランザクションの分離、およびトランザクションが正常終了または異常終了した後に中間の一時的なトランザクションの状態をメモリに保持しないために、トランザクション メソッドの ReleaseInstanceMode ビヘイビアには、ReleaseInstanceMode.BeforeAndAfterCall が設定されます。ただし、Enterprise Services ではサービスにそのビヘイビアを強制しますが、WCF サービスを作成する開発者は、トランザクションの境界を越えてインスタンスを保持することを選択できます。WCF は、処理内容を把握している開発者の指示を無視するようなことはありません。

既に説明しましたが、WCF はセッションもサポートします。セッションのサポートが必要かどうかは [ServiceContract(Session=true)] 属性の設定を使用して宣言されます。WCF では、特定のコントラクトを使用して、セッションがエンドポイント間の通信に対して確立されます。ただし、このような通信セッションの存在が、サービスのインスタンスの処理方法を制御することに直結するわけではありません。サービス インスタンスとセッションの相関関係は、[ServiceBehavior] プロパティの InstanceContextMode によって明示的に制御されます。セッションの継続中にインスタンスを維持する必要がある場合は、InstanceContextMode.PerSession を使用し、セッション スコープに関係なくすべての呼び出しに対して新しいインスタンスを生成する場合は、InstanceMode.PerCall を使用します。後者の場合、OperationContext.Current.SessionId の値として使用できるセッション識別子によって、セッションに関連付ける状態を格納したり、回復したりすることができます。

サービスのホスティング

これらすべてのインスタンス管理の選択肢に着目すると、これをだれが実行するのか、という疑問が生じます。ここまでは、インターフェイスとサービス実装クラスを扱いましたが、サービス インスタンスをアクティブ化、管理、および破棄するにはサービス ホストが必要です。

開発者側から見ると、WCF では、ホスティング サービスに代わる 2 つの方法が利用できます。これは両方ともほぼ同一な方法です。この 2 つの方法を比較した場合、簡単な方法は、ASP.NET アプリケーションの内部でサービスをホストする方法であり、柔軟性や明示性が高い方法は、選択したアプリケーション プロセス内でユーザー自身がサービスをホストする方法です。

<%@ ServiceHost Language="C#"
            CodeBehind="~/App_Code/Service.cs"
            Service="MyService" %>

ASP.NET での WCF サービスのホスティングは非常にわかりやすく、ASMX モデルによく似ています。ASP.NET Web サービスの *.asmx ファイルと同様に、サービス実装全体を *.svc ファイルに配置するか、あるいは、分離コード ファイルまたは他のアセンブリに存在するサービス実装を参照することができます。サービス実装クラスを配置 (またはコンパイル) する方法は、ASMX Web サービスを作成する一般的な方法とあまり違いはありません。@ServiceHost ディレクティブの属性も @WebService ディレクティブの属性と同じです。

WCF と ASMX の重要な違いは、WCF サービスはサービスを外部に公開する方法が正確に指定されるまで、何も実行しない点です。ASMX サービスは、 *.asmx ファイルを IIS の仮想ディレクトリに配置すると、外部との対話を自動的に開始します。WCF サービスは、ユーザーが指示、および対話方法を指定するまで、だれとも対話しません。

"ABC" の話を覚えているでしょうか。myservice.svc ファイルを Web サーバーのルート ディレクトリに配置する場合、アドレス (http://www.example.com/myservice.svc) があり、すなわち "A" です、実装と共にコントラクト (すなわち "C") がありますが、"B" がありません。つまり、サービスをこのアドレスに紐付けるためのバインディングがないため、サービスはワイヤ上での動作方法を認識していません (HTTP が必要であることを除く)。

using (ServiceHost peopleHost = new ServiceHost( typeof(People) ) )
{
   peopleHost.Open();
   Console.WriteLine("Press ENTER to close");
   Console.ReadLine();
   peopleHost.Close();
}

*.svc 拡張子を持つファイルをコンパイルして、各サービス クラスを読み込むたびに、WCF ランタイムはサービス クラスの "サービス ホスト" を自動的に作成します。WCF サービスを独自のプロセス内でホストする場合、上記のようなサービス ホストを自分で用意する必要があります。ServiceHost のインスタンス化は複雑な作業ではありません。サービス ホストは、Open したときにサービス インスタンスの管理を開始し、Close したときに終了します。

ServiceHost クラスには、インスタンスとセッションを管理するために必要なすべての機能が含まれています。また、これは特定のサービス向けのすべてのメッセージに関するエントリ ポイントでもあります。サービス ホストは、サービスをトランスポート アドレスに紐付ける (これが "B" になります) 1 つまたは複数のエンドポイントの基盤となります。

バインディングの選択と構成

既に説明したとおり、バインディングとは、サービス コントラクト実装をアドレスに紐付けることです。バインディングではさまざまな操作を行いますが、代表的なのは、適切なトランスポートの選択、ワイヤ上でメッセージをエンコードするための方法の選択 (山括弧を使用した XML のプレーンテキスト、MTOM エンコード、またはバイナリなどのメッセージ送信方法の選択)、セキュリティ機能の定義、信頼性の高いメッセージ配信プロトコルの実装に関する定義、およびセッション機能をサポートする方法の定義です。

WCF には、前に述べたように、トランスポート、エンコード、およびビヘイビアの最も一般的な組み合わせを選択するための複数の定義済みバインディングがあり、多くの場合、これらのバインディングのいずれかを選択し、必要な場合はニーズに合わせてこれを調整できます。

最も単純なバインディングは BasicHttpBinding です。これは WS-I (Web Services Interoperability Organization) Basic Profile for Web Services と互換性があるバインディング要素を組み合わせた実装です。これは、HTTP トランスポートのバインディング要素と、(UTF-8) テキスト エンコーディング、トランスポート レベルの HTTP セキュリティ、(構成済みでなければ) SOAP 1.1 エンベロープ フォーマットを組み合わせます。

using (ServiceHost peopleHost = new ServiceHost( typeof(People) )
{
   peopleHost.AddServiceEndpoint(
typeof(IPeople),
new BasicHttpBinding(),
"http://localhost:8080/people");
   peopleHost.Open();
   Console.WriteLine("Press ENTER to close");
   Console.ReadLine();
   peopleHost.Close();
}

Basic Profile を使用してサービス実装を HTTP アドレスにバインドするには、コントラクトを組み合わせるサービス ホスト、選択したバインディング、および明示的なホスティング コードのアドレスに、エンドポイントを追加する必要があります。

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
   <system.serviceModel>
      <services>
         <service name="SelfHostedService.People">
            <endpoint address="http://localhost:8080/people"
                     binding="basicProfileBinding"
                     contract="SelfHostedService.IPeople"/>
         </service>
      </services>
   </system.serviceModel>
</configuration>

ソース コードにバインディングの詳細を組み込みたくない場合 (これは非常に柔軟性が高いため、最も一般的なケースです)、アプリケーション構成ファイルにサービス エンドポイントを設定できます。また ASP.NET のホスティング サービスの場合は web.config ファイルに設定できます。これを行うには、<service> サブセクションを持つ <system.serviceModel> 構成セクションが必要です。<service> サブセクションには、アプリケーションがホストしている各サービスの <service> 要素が含まれています。構成したサービス クラスは、name 属性によって識別されます。

サービスが公開する各エンドポイントは、各 <service> 要素の下にある <endpoint> 要素によって構成されます。address 属性はサービスのネットワーク アドレスを指定し、binding は使用する定義済み、またはカスタム バインディングを参照し、contract はエンドポイント コントラクトの WCF [ServiceContract] を保持するインターフェイスを指します。クラスが複数のサービス コントラクトを実装する場合、コントラクトごとにエンドポイントを構成する必要があり、すべてのコントラクトが個別のネットワーク アドレスに属する必要があります。エンドポイントが構成されていないコントラクト実装には接続できません。すべてが明示的に構成される必要があります。

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
   <system.serviceModel>
      <client>
         <endpoint name="myClient"
                  address="http://localhost:8080/people"
                 binding="basicHttpBinding"
                 bindingConfiguration="MyBpBinding"
                 contract="SelfHostedService.IPeople"/>
      </client>
      <bindings>
         <basicHttpBinding>
            <binding name="MyBpBinding">
               <security mode="TransportCredentialOnly">
                  <transport clientCredentialType="Digest" realm="example.com" />
               </security>
            </binding>
         </basicHttpBinding>
         <wsHttpBinding>
            <binding name="MyWsBinding">
               <security mode="Message">
                  <message clientCredentialType="Windows" defaultProtectioinLevel="EncryptionAndSign" />
               </security>
            </binding>
         </wsHttpBinding>
         <netTcpBinding>
            <binding name ="MyTcpBinding">
               <security mode="Message">
                  <message clientCredentialType="Windows" defaultProtectioinLevel="EncryptionAndSign" />
               </security>
            </binding>
         </netTcpBinding>
      </bindings>
      <services>
         <service name="SelfHostedService.People">
            <endpoint address="https://localhost:8079/people"
                     binding="basicHttpBinding"
                     bindingConfiguration="MyBpBinding"
                     contract="SelfHostedService.IPeople"/>
            <endpoint address="http://localhost:8080/peopleWS"
                     binding="wsHttpBinding"
                     bindingConfiguration="MyWsBinding"
                     contract="SelfHostedService.IPeople"/>
            <endpoint address="net.tcp://localhost:8081/people"
                     binding="netTcpBinding"
                     bindingConfiguration="MyTcpBinding"
                     contract="SelfHostedService.IPeople"/>
         </service>
      </services>
   </system.serviceModel>
</configuration>

サービスは、実装するコントラクトごとに、任意の数のエンドポイントを定義できます。HTTPS 上でトランスポートレベルの認証を使用した BasicHttpBinding や、HTTP 上でメッセージ レベルの認証である WS-Security を使用した WSHttpBinding、あるいはバイナリ TCP トランスポート上の既定でセキュアな NetTcpBinding を使用してサービスがメッセージをリッスンする場合、構成にそれぞれのエンドポイントを追加することで、クライアントは、必要なすべてのエンドポイントでサービスと対話できます。サービスは、これらすべてのエンドポイントを並行してリッスンします。/p>

上記の例でわかるように、定義済みの各バインディングでは、それぞれの機能をある程度カスタマイズできます。<bindings> セクションには、バインディングごとに、WCF が事前に定義したサブセクションがあります。これらのサブセクションを、必要な数だけカスタマイズすることができます。エンドポイントに使用される具象的なバインディングは、binding とそれぞれの bindingConfiguration を指定することによって選択できます。

バインディングの要件と検証

すべての呼び出し元 (またはすべてのメッセージの送信元) で認証を必要とするサービス実装があるとします。あるいはサービスは、配信中にメッセージが紛失しないように、すべてのメッセージが信頼性の高いトランスポートによって配信されることを要求するとします。この場合、詳細な設定がない単純な ASMX 互換の BasicHttpBinding はセキュアでなく、また信頼性の高いメッセージングをサポートしないという問題が発生します。

バインディングの選択は実装元 (構成ファイルの作成者) に依存するため、開発者はサービス エンドポイントに関する最小限の要件を定義する方法を決めておく必要があります。セッション サポートの要求などの、一部の要件は、[ServiceContract] 属性で示されています。また、[DeliveryRequirements] 属性などを使用して、その他の要件を表すことができます。

[DeliveryRequirements(
 RequireOrderedDelivery=true)
public class People : IPeople
{ ... }

[DeliveryRequirements] 属性を使用すると、開発者はサービスをクライアントに公開するためのバインディングに (この場合はメッセージの配信方法に関しての) 制約を加えることができます。このような制約は、たとえば、サービスのアーキテクチャがキューの優先されているサービスを必要とし、信頼性の高い配信チャネルを介して送信されるメッセージのみを受け取る場合などに、重要となります。実装元が任意のトランスポートとバインディングを使用することを開発者が許可すると、サービスに対して定義されたアーキテクチャ ルールに違反し、結果として、負荷が高い劣悪なパフォーマンスとスループット、さらに深刻な場合はデータ損失につながります。実装元がアプリケーション アーキテクチャに関する詳細のすべてを把握していない場合があるため、開発者は、これらのルールの強制を WCF ランタイムに対して要求するための方法として [DeliveryRequirements] を使用できます。

サービスのバインディング要件は、サービスに対するサービス ホストが Open されるときに、実際のバインディング機能と比較して検証されます。エンドポイント バインディングがサービスのバインディング要件をサポートしない場合、それぞれのバインディング検証元 (WCFが提供) によって例外がスローされ、サービス ホストは不適切な動作またはセキュリティ ホールを回避するために、開始を拒否します。

クライアント

ここまでは、サービスのサーバー側の詳細について説明してきましたが、クライアント側の詳細についてはまだ触れていませんでした。WCF はまさしく "対称的" です。アドレス、バインディング、およびサービス コントラクトに関して取得した知識の多くは、クライアントにも同様に適用されます。サービス クライアントの実装では、ほとんどの場合、ASP.NET Web サービスと同様に適切なツールの使用が課題となります。

Visual Studio 2005 がインストールされたマシンに WCF をインストールすると、WCF は独自の "インポータ" を Visual Studio に追加します。このインポータは、"Web 参照" をサービスに追加するたびにASP.NET プロキシが発行されるのと同じように、WCF コントラクト定義と WCF サービス プロキシを発行します。これによって、WCF サービス クライアントがほぼ自動的に実装されます。少なくとも現在のベータ ビットでは、このインポータは、サービス メタデータに存在するサービスのバインディング情報を、アプリケーション構成ファイルに組み込むといった処理は実行しません。

Svcutil.exe /config:myclient.exe.config /out:myclient.cs
http://www.example.com/myservice.svc?wsdl

ただし、この記事の冒頭で紹介したスタンドアロン ツール svcutil.exe は、コントラクト情報をインポートして、コントラクト プロキシを生成するだけでなく、/config スイッチが使用できます。これはサービス エンドポイントのバインディング メタデータのポリシー情報を、サービス バインディングと互換性のあるクライアント側のカスタム バインディング構成に変換します。

using (ChannelFactory ipeopleFactory =
new ChannelFactory<IPeople>(
            new BasiHttpBinding(),
new EndpointAddress("http://localhost:8080/people")))
{
   ipeopleFactory.Open();
   IPeople proxy = ipeopleFactory.CreateChannel();
   proxy.StorePerson( ... );
   ipeopleFactory.Close();
}

しかし、実際にはこのツールを使用する必要はありません。[ServiceContract] 宣言を参照するか、あるいはこれをクライアント コードに直接コピーすることができます。サービス コントラクト宣言コードの参照またはコピーは、実際には "共有コントラクト" というサービス指向の概念に従っています。メタデータの定義が単純にコピーされるだけです。メタデータの記述が WSDL か CLR 属性であるかということは問題ではありません。サービス コントラクト宣言が格納されているアセンブリを作成し、配布して、サーバー側とクライアント側の両方からこのアセンブリを参照することができます。これによりサーバーとクライアント間の相互結合がわずかに密接になります。これは、サーバー側とクライアント側はバインディングの個々の要求と要件を追加して、コントラクトを個別に修正することができないためです。ただし、WSDL だけでコントラクトを共有するか (ツールを使用)、あるいはコードベースの宣言またはコンパイル済みのメタデータを共有するかは、自由に選択できます。どれを選択しても正常に動作します。

クライアントの構築は、サービス コントラクトのための ChannelFactory<T> を作成する単純なものです。コントラクト ("C") が組み込まれたチャネル ファクトリは、アドレスとバインディングによって論理的に構成し、WCF の "ABC" を満たす必要があります。

ターゲット アドレスとバインディングの指定は、チャネル ファクトリのコンストラクタの呼び出しに対して明示的に行うことができます。また、サーバー側で行う場合と同様に、アプリケーション構成ファイルを介して間接的に実行できます。構成ファイルの使用は、コードベースの代替方法よりも効率的です。これは、構成の使用には柔軟性があり、サーバー側の定義へアクセス可能であれば、この定義をクライアント側の構成にコピー&ペーストすることができるためです。これは、サーバー側でも同様です。

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
   <system.serviceModel>
      <client>
         <endpoint name="myClient"
                 address="http://localhost:8080/people"
                 binding="basicProfileBinding"
                 contract="SelfHostedService.IPeople"/>
      </client>
   </system.serviceModel>
</configuration>

構成ファイルの <client> セクションとチャネル ファクトリ間のリンクは、チャネル ファクトリのジェネリック型の引数に対応する 1 つ以上の <endpoint> 定義上の contract 属性、および使用する構成の ChannelFactory<T> コンストラクタの引数に対応する name 値を基に設定されます。

using (ChannelFactory<IPeople> ipeopleFactory =
new ChannelFactory<IPeople>("myClient"))
{
   ...
}

チャネル ファクトリがあれば、直接的または間接的な構成に関係なく、これを開始することができます。これにより、コントラクトのバインディング要件でバインディングをチェックする検証プロセスが開始されます。これは、サーバー側でも同様です。検証例外が発生しなかった場合、チャネル ファクトリ上で CreateChannel() を呼び出し、返されたサービス コントラクト インターフェイス プロキシの実装でメソッドを呼び出すことで、サービスと共に新しいセッションを開始できます。

まとめ

WCF は、分散システムの構築、メッセージング形式と RPC 形式の通信の統合、プラットフォームに最適化された効率的なバイナリ通信とオープンスタンダードに準拠した Web サービス、および堅牢なセキュリティを実現する豊富な機能セット、スケーラブルで信頼性の高い通信パスとトランザクション プロセスという、さまざまな機能を備えたテクノロジ プラットフォームです。

これらの優れた機能を別にしても、(少なくとも著者にとっては) 簡単でわかりやすいプログラミング モデルと、サービスの構成方法に柔軟性があるという点には本当に目を見張るものがあります。専門家らしい表現ではありませんが、WCF を使用すると強力なサービスを簡単に構築することができ、この構築作業がとても楽しく感じられます。

この情報は役に立ちましたか。
(残り 1500 文字)