印刷用ページ       送信     
クリックして評価とフィードバックをお寄せください
 サービス ステーション : WCF ルーターを構築する (第 2 部)
Related Articles

Udi Dahan が、大規模な Software Plus Services 取引アプリケーションの開発中に、予測していなかった問題を彼らのチームがどのようにして識別し、乗り越えていったのかを説明します。

Udi Dahan

MSDN Magazine April 2009

...

Read more!

ここでは、John Papa が、WCF を使用する通信を介してビジネス エンティティやデータベースと対話する Silverlight 2 ユーザー インターフェイスの作成方法を紹介します。

John Papa

MSDN Magazine September 2008

...

Read more!

トランザクションを使用した状態の管理とエラーからの回復が今月の「基礎」コラムのトピックです。

Juval Lowy

MSDN Magazine January 2009

...

Read more!

Windows Communication Foundation (WCF) では、サービスにおける承認を実装するために、簡単なロールベースのシステムと、さらに強力かつ複雑なクレーム ベースの API を提供します。

Dominick Baier and Christian Weyer

MSDN Magazine October 2008

...

Read more!

今月号で Juval Lowy は、長時間実行されるワークフローの状態管理を改善するために、プロキシやサービスをいつメモリに保持すればよいかなどの疑問に取り組んでいます。

Juval Lowy

MSDN Magazine October 2008

...

Read more!

Also by this Author

ここでは、WCF クライアントおよびサービスについて見ていきます。また、アドレス指定とメッセージ フィルタの動作を調整してサービスを適切にルーティングする方法について説明します。

Michele Leroux Bustamante

MSDN Magazine April 2008

...

Read more!

今回は、"Geneva" というコード ネームを持つ Microsoft の新しいフレームワークをご紹介します。これは、クレームベースのアプリケーションやサービス、およびフェデレーション セキュリティ シナリオを構築するために使用できます。

Michele Leroux Bustamante

MSDN Magazine December 2008

...

Read more!

セキュリティ トークン サービス (STS) は、呼び出し元を認証するセキュリティ ゲートウェイとして機能し、呼び出し元を表すクレームを保持するセキュリティ トークンを発行します。この記事では、"Geneva" Framework を使用してカスタム STS を構築する方法について説明します。

Michele Leroux Bustamante

MSDN Magazine January 2009

...

Read more!

Popular Articles

Writing a Web application with ASP.NET is unbelievably easy. So many developers don't take the time to structure their applications for great performance. In this article, the author presents 10 tips for writing high-performance Web apps. The discussion is not limited to ASP.NET applications because they are just one subset of Web applications.

Rob Howard

MSDN Magazine January 2005

...

Read more!

Ray Djajadinata

MSDN Magazine May 2007

...

Read more!

WPF は、.NET Framework 3.0 の最も重要な新しいテクノロジの 1 つです。今月は、John Papa がそのデータ バインド機能について紹介します。

John Papa

MSDN Magazine December 2007

...

Read more!

Microsoft patterns & practices の Composite Application Guidance for WPF で複合アプリケーションを作成する利点を紹介します。

Glenn Block

MSDN Magazine September 2008

...

Read more!

SQL Server 2005 で正規表現を使用して、効率的で高度なテキスト分析を実行できます。

David Banister

MSDN Magazine February 2007

...

Read more!

サービス ステーション
WCF ルーターを構築する (第 2 部)
Michele Leroux Bustamante

コードのダウンロード : ServiceStation2008_06.exe (340 KB)
オンラインでのコードの参照

2008 年 4 月号のサービス ステーションでは、呼び出し元のクライアントとターゲット サービスとの間でメッセージが透過的に流れるようにする単純なルーターの作成方法を示しました。そこでは、Windows® Communication Foundation (WCF) のアドレス指定とメッセージ フィルタに関する重要なセマンティクスについて確認し、型指定のないメッセージを処理するルーター コントラクトの設計方法を学習し、メッセージがルーターで変更されずに通過できるようにするバインドおよび動作の構成方法を学習しました。今回の記事では、議論の続きとして、より実際的なシナリオをルーターに採用する場合に問題となる、実装の詳細についてより詳しく見ていきます。

パススルー ルーターのシナリオ
第 1 部で述べたように、クライアントとサービスの間にパススルー ルーターを挿入した場合、クライアントはルーターとの間にではなくターゲット サービスとの間に関係を結ぶことになります。メッセージは確かに、ルーターが理解できるトランスポート プロトコルとメッセージ エンコーダを使用して送信する必要がありますが、メッセージの内容全体 (たとえば、セキュリティ ヘッダーおよび信頼できるセッションを含む) をルーターでは処理しません。パススルー ルーターが適用する可能性のあるわずかな処理としては、負荷分散、コンテンツベースのルーティング、メッセージ変換などがあります。
サーバー リソース間での負荷分散および作業の分配には、ネットワーク負荷分散 (NLB) が有効であり、またはハードウェア負荷分散デバイスを用いるとさらに効果的です。それでも、そのような機能を持たない環境でサーバーをホストしている場合や、直接制御できない物理インターフェイスにサービスがインストールされている場合、またはドメイン固有のロジックに基づいたルーティングが必要な場合や、アプリケーションが単純に、構成の簡単な軽量のルーティング ソリューションを必要としている場合などには、WCF ルーターを負荷分散に利用できます。そのような WCF ルーターを使用することで、同じマシン上の複数のプロセスや、マシン間で分散された複数のプロセスでホストされているサービスに対して、メッセージを配信できます。
使用する分散モデルに関係なく、負荷分散ルーターには必ずいくつかの中核機能が必要となります。サービスを負荷分散に含めるには、そのサービスを何らかの方法でルーターに登録する必要があります。ルーターは、正しくメッセージを転送するために、サービスの種類および関連付けられたエンドポイントを決定できる必要があります。また、ルーターには、古典的なラウンドロビン アプローチや、何らかの形式の優先度ベースのルーティングなど、負荷を分散するためのアルゴリズムが必要です。
負荷分散とは対照的に、サービス間のメッセージ配信がメッセージの内容に基づいて処理される場合もあります。一般に、コンテンツ ベースのルーターは、メッセージ ヘッダーまたはメッセージ本文を検査してルーティング情報を取得します。たとえば、有効なライセンス キーを持つクライアントからのメッセージは、高い処理能力を持つサーバー マシンの大きなプールに、高い優先度で転送されますが、試用ライセンスによるメッセージは、能力の比較的低いサーバーの小さなプールに転送されます。このシナリオでは、ルーターはメッセージの転送先を知る必要があるだけでなく、転送先を決定する前に各メッセージとそのヘッダーまたは本文の内容を検査する能力を持つ必要があります。以降のセクションでは、これらのシナリオをサポートする関連ルーティング機能を説明します。

Action ヘッダーによる転送
ルーターで受信されたメッセージには、メッセージを正しいサービスに転送するために利用できる 2 つのアドレス指定ヘッダーがあります。
To: このヘッダーは、エンドポイントの名前を示します。このヘッダーがターゲット サービスと一致し、ルーターと一致しない場合は、メッセージの宛先であるサービス エンドポイントの URL を示しています。
Action: このヘッダーは、メッセージの目的であるサービス操作を示しますが、必ずしも有効な URL 自体を表すものではありません。
ただし、多くの場合、To ヘッダーはルーター アドレスと一致してサービスとは一致せず、Action ヘッダーがメッセージの正しい宛先に関するより信頼できる情報源となります。Action ヘッダーは、サービス コントラクトの名前空間、サービス コントラクト名、および操作名から派生されることを思い出してください。これは、コントラクトが異なる種類のサービス間で共有されていないと仮定した場合、ルーターがターゲット サービスを一意に識別するために十分な情報となります。次に示すサービス コントラクトが、それぞれ異なる種類のサービスに実装されていると考えてください。
[ServiceContract(Namespace = 
"http://www.thatindigogirl.com/samples/2008/01")]
public interface IServiceA {
  [OperationContract]
  string SendMessage(string msg);
}
[ServiceContract(Namespace = 
  "http://www.thatindigogirl.com/samples/2008/01")]
public interface IServiceB {
  [OperationContract]
  string SendMessage(string msg);
}
public class ServiceA : IServiceA {...}
public class ServiceB : IServiceB{...}
図 1 に示されるように、ルーターは、各サービス コントラクトのコントラクト名前空間とメッセージの宛先であるサービス エンドポイントとの間のマッピングを利用できます。
図 1 コントラクト名前空間をサービス エンドポイントにマッピングする (クリックすると拡大画像が表示されます)
次のコードに示す辞書は、各コントラクト名前空間エントリと、使用する正しいチャネル構成設定を示す構成要素との間で、マッピングを行うよう初期化されています。
static public IDictionary<string, string> RegistrationList = 
  new Dictionary<string, string>();

RegistrationList.Add(
  "http://www.thatindigogirl.com/samples/2008/01/IServiceA", 
  "ServiceA");
RegistrationList.Add(
  "http://www.thatindigogirl.com/samples/2008/01/IServiceB", 
  "ServiceB");
チャネルを初期化するコードは、次のようになります。
string contractNamespace = 
  requestMessage.Headers.Action.Substring(0, 
  requestMessage.Headers.Action.LastIndexOf("/"));

string configurationName = 
  RouterService.RegistrationList[contractNamespace];

using (ChannelFactory<IRouterService> factory = 
  new ChannelFactory<IRouterService>(configurationName))
{...}
このシナリオには、注意を要する重要な設計上の依存事項がいくつかあります。
  • 構成を単純にし、複数のルーター インスタンスをサポートするために、ほとんどの場合、コントラクトからサービスへのマッピングはデータベース内に保持されます。
  • コントラクトを実装するすべてのサービスでメッセージを処理できる場合を除き、サービス コントラクトを複数の種類のサービスに実装することはできません。
  • サーバー ファーム内にサービスのインスタンスが複数ある場合、各エンドポイントに対する構成は、物理的なロード バランサが後で適切に分散できるような仮想アドレスにマッピングされる必要があります。
  • アプリケーション サービス用以外のアクション ヘッダーを含むメッセージはサポートされません。
この最後の点は重要です。なぜなら、アプリケーション サービスに対して安全なセッションや信頼できるセッションが有効になっている場合、実際のアプリケーション サービス メッセージの前に、これらのセッションを確立するために追加のメッセージが送信されるためです。これらのメッセージは、それぞれのプロトコルに対して Action ヘッダーを使用し、アプリケーション サービスとは完全に独立しています。これは、メッセージの転送に、Action ヘッダーの代替手段を使用する必要があることを意味します。

カスタム ヘッダーを使用した転送
クライアントが通信しようとするアプリケーション サービスを適切に示すルーティング ヘッダーがすべてのメッセージに確実に含まれるようにするため、次に示すように、アプリケーション サービスのエンドポイント構成セクションにカスタム ヘッダーを指定できます。
<service behaviorConfiguration="serviceBehavior" 
  name="MessageManager.ServiceA">
  <endpoint address="http://localhost:8010/RouterService"
    binding="wsHttpBinding" bindingConfiguration="wsHttp" 
    contract="IServiceA" listenUri="ServiceA">
    <headers>
      <Route 
        xmlns="http://www.thatindigogirl.com/samples/2008/01">
        http://www.thatindigogirl.com/samples/2008/01/IServiceA
      </Route>
    </headers>
  </endpoint>
</service>
カスタム ヘッダーには、名前、名前空間、および値があります。ヘッダーがこれより動的な場合もありますが、ここでは、ヘッダーはサービス コントラクトの名前空間を表すように固定されています。Route 要素がヘッダー名を示し、名前空間は xmlns 属性によって示されます。このヘッダーはエンドポイント構成の一部として指定されるため、サービスのメタデータに含まれます。そのため、クライアントがプロキシを生成するときには、次に示すように、ヘッダーを含むクライアント構成も生成します。
<client>
  <endpoint address="http://localhost:8010/RouterService" 
    binding="wsHttpBinding" bindingConfiguration="wsHttp"
    contract="localhost.IServiceA" >
    <headers>
      <Route xmlns="http://www.thatindigogirl.com/samples/2008/01">
        http://www.thatindigogirl.com/samples/2008/01/IServiceA
      </Route>
    </headers>
  </endpoint>
</client>
これにより、ヘッダーの存在がクライアントのコーディング作業に対して透過となり、安全なセッションや信頼できるセッションを確立するためのメッセージも含めて、すべてのメッセージにヘッダーが含まれるようになります。ルーターは、次に示すように、どのメッセージからも名前と名前空間によってヘッダー値を取得できます。
string contractNamespace = 
  requestMessage.Headers.GetHeader<string>(
  "Route", 
  "http://www.thatindigogirl.com/samples/2008/01");
この実装が前の例から変更されている唯一の点は、ルーターがコントラクトの名前空間を確認する方法であり、Action ヘッダーの代わりにカスタム Route ヘッダーを使用しています。これにより、ルーターは、安全なセッションまたは信頼できるセッションに関連したメッセージを適切なサービス エンドポイントに転送できます。

サービスを登録する
アプリケーション サービスのエンドポイントをハードコーディングする代わりに、ルーターは、サービスがオンラインまたはオフラインになったときにサービスを登録または登録解除できるように、サービスのエンドポイントを公開することができます。これにより、ソフトウェアまたはハードウェアのロード バランサが存在しない場合には、アプリケーション サービスのスケール アウトが必要なときや、各エンドポイント アドレスのポートまたはマシン名が変更されたときに、ルーターの構成オーバーヘッドが低減されます。このモデルをサポートするには、次の手順を実行します。
  • ルーターに対してサービス登録コントラクトを実装し、ファイアウォールの背後のアプリケーション サービスにエンドポイントを公開します。
  • ルーターに対して登録リストを保持します。
  • 各 ServiceHost の初期化後に、サービス エンドポイントをルーターに登録します。
  • 各 ServiceHost が障害状態となったとき、または終了したときに、サービス エンドポイントをルーターから登録解除します。
図 2 に示す登録プロセスでは、物理エンドポイント アドレスにマッピングされたコントラクト名前空間を保持するエントリが追加されています。
図 2 サービスをルーターに登録する (クリックすると拡大画像が表示されます)
このアプローチの場合、登録に必要なのは、各サービス エンドポイントのコントラクト名前空間と物理アドレスだけです。図 3 に、登録および登録解除時にルーターに渡される、IRegistrationService サービス コントラクトおよび関連付けられた RegistrationInfo の詳細を示します。
[ServiceContract(Namespace = 
  "http://www.thatindigogirl.com/samples/2008/01")]
public interface IRegistrationService {
  [OperationContract]
  void Register(RegistrationInfo regInfo);

  [OperationContract]
  void Unregister(RegistrationInfo regInfo);
}

[DataContract(Namespace = 
  "http://schemas.thatindigogirl.com/samples/2008/01")]
public class RegistrationInfo {
  [DataMember(IsRequired = true, Order = 1)]
  public string Address { get; set; }

  [DataMember(IsRequired = true, Order = 2)]
  public string ContractName { get; set; }

  [DataMember(IsRequired = true, Order = 3)]
  public string ContractNamespace { get; set; }

  public override int GetHashCode()   {
    return this.Address.GetHashCode() + 
    this.ContractName.GetHashCode() + 
    this.ContractNamespace.GetHashCode();
  }
}
ルーターはコントラクトごとに 1 つのエントリを格納することもできますが、その場合、各コントラクトに対して複数のサービスは使用できません。複数のエントリにまたがる分散をサポートするには、ルーターは登録ごとに一意のキーを使用する必要があります。このコードでは、各エントリに対して RegistrationInfo インスタンスのハッシュ コードを一意に関連付けるための辞書を使用しています。
// registration list
static public IDictionary<int, RegistrationInfo> 
  RegistrationList = 
  new Dictionary<int, RegistrationInfo>();

// to register
if (!RouterService.RegistrationList.ContainsKey(
  regInfo.GetHashCode())) {
  RouterService.RegistrationList.Add(regInfo.GetHashCode(), 
    regInfo);
  }

  // to unregister
  if (RouterService.RegistrationList.ContainsKey(
    regInfo.GetHashCode())) {
    RouterService.RegistrationList.Remove(
      regInfo.GetHashCode());
  }
ルーターは、メッセージを受信すると、コントラクト名前空間を収集し、辞書内で一致する適切な項目を探します。一致する項目が複数ある場合は、選択条件を使用して、適切なサービス エンドポイントにメッセージを送信します (図 4 を参照)。
string contractNamespace = 
  requestMessage.Headers.Action.Substring(0, 
  requestMessage.Headers.Action.LastIndexOf("/"));

// get a list of all registered service entries for 
// the specified contract
var results = from item in RouterService.RegistrationList
  where item.Value.ContractNamespace.Contains(contractNamespace)
  select item;

int index = 0;
// find the next address used ...

// create the channel 
RegistrationInfo regInfo = results.ElementAt<KeyValuePair<int, 
  RegistrationInfo>>(index).Value;

Uri addressUri = new Uri(regInfo.Address);
Binding binding = ConfigurationUtility.GetRouterBinding    (addressUri.Scheme);
EndpointAddress endpointAddress = new EndpointAddress(regInfo.Address);

ChannelFactory<IRouterService> factory = new 
  ChannelFactory<IRouterService>(binding, endpointAddress)
// forward message to the service ...
動的登録は、サービスに対する負荷分散のニーズをマシン間で分散する以外にも、同じマシンでサービスの複数のインスタンスがホストされる可能性があるような状況でも有用です。そのようなインスタンスが Windows サービスでホストされる場合は、複数のポート割り当てが必要となります。
これをサポートするには、サービスがマシンに対して動的ポート割り当てを選択する必要があります。TCP サービスの場合、これはエンドポイント構成でリッスン URI モードを Unique に設定することで実現できます。
<endpoint address="net.tcp://localhost:9000/ServiceA" 
  contract=" IServiceA" binding="netTcpBinding"
  listenUriMode="Unique"/>
ただし、名前付きパイプおよび HTTP に対しては、この設定で一意のポートは選択されません。その代わりに、アドレスに GUID が付加されます。
net.tcp://localhost:64544/ServiceA
http://localhost:8000/ServiceA/66e9c367-b681-4e4f-8d12-80a631b7bc9b
net.pipe://localhost/ServiceA/6660c07e-c9f5-450b-8d40-693ad1a71c6e
TCP および HTTP サービス エンドポイントに対して一意のポートを割り当てるには、コードでベース アドレスまたは明示的なエンドポイントを初期化します。
Uri httpBase = new Uri(string.Format(
  "http://localhost:{0}", 
  FindFreePort()));
Uri tcpBase = new Uri(string.Format(
  "net.tcp://localhost:{0}", 
  FindFreePort()));
Uri netPipeBase = new Uri(string.Format(
  "net.pipe://localhost/{0}", 
  Guid.NewGuid().ToString()));

ServiceHost host = new ServiceHost(typeof(ServiceA), 
  httpBase, tcpBase, netPipeBase);
図 5 では、同じマシンでホストされた複数のサービスをルーターに登録しています。また、この図では、ルーターの単一点障害を取り除くために、インスタンス間で登録呼び出しを分散させるソフトウェアまたは物理ロード バランサが必要な場合があることが示されています。もちろん、登録リストが共有データベースに格納されることも示されています。
図 5 負荷分散ルーターをとおしてサービスを動的ポートに登録する (クリックすると拡大画像が表示されます)

メッセージを検査する
一般にルーターは元のメッセージをアプリケーション サービスに転送しますが、メッセージの内容に基づいた操作を実行する場合もあります。たとえば、コンテンツ ベースのルーティングでヘッダーまたは本文要素を検査したり、ヘッダーまたは本文要素の有効性に基づいてメッセージを拒否します。
Message 型では、アドレス指定ヘッダーを直接取得したり、カスタム ヘッダーを名前と名前空間で取得するための Headers プロパティを公開しているため、ヘッダーの検査は簡単です。次のサービス操作を考えてください。ここでは、メッセージ コントラクトを使用して、受信操作に対するカスタム LicenseKey ヘッダーを追加しています。
// operation
[OperationContract]
SendMessageResponse SendMessage(SendMessageRequest message);

// message contract
[MessageContract]
public class SendMessageRequest {
  [MessageHeader]
  public string LicenseKey { get; set; }

  [MessageBodyMember]
  public string Message { get; set; }
}
クライアントは、LicenseKey ヘッダーを含むメッセージを送信します。ライセンス キーを持っていない場合、このヘッダーは空の可能性があります。ルーターは、このヘッダーを次のようにして取得できます。
string licenseKey = 
  requestMessage.Headers.GetHeader<string>(
  "LicenseKey", 
  "http://www.thatindigogirl.com/samples/2008/01");
同じ LicenseKey 値がメッセージ本文内で渡される場合には、ルーターはメッセージ本文を読み取って値にアクセスする必要があります (この情報が Message 型から直接得られないため)。GetReaderAtBodyContents メソッドは、メッセージ本文を読み取るのに使用できる XmlDictionaryReader を次のように返します。
XmlDictionaryReader bodyReader = 
  requestMessage.GetReaderAtBodyContents();
Message の State プロパティは、MessageType の列挙値 Created、Copied、Read、Written、または Closed になります。メッセージは Created 状態から始まり、操作に対して Message パラメータを受信したルーターはメッセージを処理しないため、状態は Created のままです。
メッセージ本文を読み取ると、要求メッセージの状態が Created から Read に遷移します。メッセージの読み取り、書き込み、またはコピーは 1 回しか行えないため、いったん読み取られたメッセージはアプリケーション サービスに転送できません。
コンテンツ ベースのルーター実装では、メッセージを読み取る前に、メッセージをバッファにコピーする必要があります。このメッセージのバッファ コピーを使用して、元のメッセージの新しいコピーを次のように作成して処理に使用できます。
MessageBuffer messageBuffer = 
  requestMessage.CreateBufferedCopy(int.MaxValue);
Message messageCopy = messageBuffer.CreateMessage();
XmlDictionaryReader bodyReader = 
  messageCopy.GetReaderAtBodyContents();

XmlDocument doc = new XmlDocument();
doc.Load(bodyReader);
XmlNodeList elements = doc.GetElementsByTagName("LicenseKey");
string licenseKey = elements[0].InnerText;
同じバッファを再度使用して、アプリケーション サービスに転送するメッセージを作成できます。この CreateMessage の呼び出しは、元のメッセージに基づいた新しい Message インスタンスを返します。

ルーターとトランスポート セッション
パススルー ルーターの場合、クライアントはルーターで想定しているトランスポート プロトコルおよびエンコード形式でメッセージを送信する必要があり、ルーターは、アプリケーション サービスで想定しているトランスポート プロトコルおよびエンコード形式でメッセージをアプリケーション サービスに転送する必要があります。ここまでに説明したすべてのルーティング機能は、転送の両方の側が共に HTTP (セッションあり、またはセッションなし) の場合には問題なく動作します。ただし、TCP などのトランスポート セッションを導入すると、いくつかの興味深い課題が発生します。セキュリティが無効で信頼できるセッションがないという最も単純なケースではすべてがうまくいきますが、これらの機能を追加したときに問題が生じます。
アプリケーション サービスに対してセキュリティを有効にした場合、ルーターは署名付きの To ヘッダーを提供する必要があります。これは通常、To ヘッダーをクライアントが送信した状態のまま変更しないことを意味しますが、既定では、手動アドレス指定が有効でない限り、ルーターはメッセージが送信されたときにサービスのアドレスと一致するよう To ヘッダーを変更します。たとえば、ルーターが TCP プロトコルを使用してメッセージをサービスに転送する場合、送信チャネルが要求/応答コントラクトに基づいているときには手動アドレス指定が許可されません。
信頼できるセッションが有効で、ルーターが TCP プロトコルを使用してサービスを呼び出す場合には、また別の問題が生じます。この場合は、ルーターをとおして非同期の受信確認が返されます。したがって、ルーターは非同期の受信確認を受け取るためにサービスとのセッションを維持する必要があります。その結果、クライアントは同じ非同期の受信確認を受け取るために、ルーターとの二重セッションを維持する必要があります。
どちらの問題も、セッションをサポートして受信/送信の二重チャネルを使用するルーターを実装することで、部分的に解決できます。呼び出し元のクライアントもアプリケーション サービスもこれを直接認識する必要はありません。これはルーター内の実装詳細です。ただし、これにはセッション対応のバインディングが必要であり、また、信頼できるセッションで非同期の受信確認を使用する場合には二重通信が必要となります。

二重ルーター
図 6 のコードは、クライアント、ルーター、およびアプリケーション サービスの間でメッセージが TCP を介して送信される状況をサポートするための、二重ルーター コントラクトの例を示しています。これは、従来の要求/応答ルーター コントラクトとは次のように異なります。
  • ProcessMessage が一方向操作です。
  • サービス コントラクトにセッションが必要であり、コールバック コントラクトが関連付けられています。この場合、クライアントはコールバックを実装する必要がないことに注意してください。これはルーター内部で処理されます。
  • コールバック コントラクトには、ルーター呼び出しからアプリケーション サービスへの応答を受信する 1 つの一方向メソッドがあります。また、サービスは応答がコールバック チャネルに送信されていることを認識しません。これらの応答には要求/応答メッセージを使用できます。
[ServiceContract(Namespace = 
  "http://www.thatindigogirl.com/samples/2008/01", 
  SessionMode = SessionMode.Required, 
  CallbackContract = typeof(IDuplexRouterCallback))]

public interface IDuplexRouterService {
  [OperationContract(IsOneWay=true, Action = "*")]
  void ProcessMessage(Message requestMessage);
}

[ServiceContract(Namespace = 
  "http://www.thatindigogirl.com/samples/2008/01", 
  SessionMode = SessionMode.Allowed)]
public interface IDuplexRouterCallback {
  [OperationContract(IsOneWay=true, Action = "*")]
  void ProcessMessage(Message requestMessage);
}
二重ルーターのアーキテクチャを図 7 に示します。クライアントの観点から見ると、要求が送信され、同期応答を期待します。ルーターは一方向操作への要求を受信し、応答を送信するためにクライアントのコールバック チャネルを保存します。同時に、ルーターは二重チャネルを使用してメッセージを転送し、サービスから応答を受信するためのコールバック チャネルを提供します。
図 7 二重ルーターのアーキテクチャ (クリックすると拡大画像が表示されます)
サービスは要求を受信し、同期応答を送信します。この応答はルーターのコールバック チャネルで受信されます。このコールバック チャネルは、次にクライアントのコールバック チャネルを使用して、クライアントに応答を返します。最初から最後まで、操作は同期して動作しますが、ルーターはアクティビティを分離し、基になる受信および送信チャネル内で二重通信を利用してメッセージを相関付けます。
これを行うルーターの実装を図 8 に示します。要求/応答ルーターの実装に対していくつかの関連した変更があります。まず、このルーターはセッションをサポートし、二重コントラクトを実装します。ルーターがサービスにメッセージを転送するときには、DuplexChannelFactory<T> を使用して二重チャネルが作成されます。これは、サービスから応答を受信するコールバック オブジェクトを提供することを意味します。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, 
  ConcurrencyMode = ConcurrencyMode.Multiple, 
  AddressFilterMode=AddressFilterMode.Any, 
  ValidateMustUnderstand=false)]

public class DuplexRouterService : IDuplexRouterService, IDisposable {
  object m_duplexSessionLock = new object();
  IDuplexRouterService m_duplexSession;

  public void ProcessMessage(Message requestMessage)  {
    lock (this.m_duplexSessionLock)  {
      if (this.m_duplexSession == null) {
        IDuplexRouterCallback callback = 
          OperationContext.Current.GetCallbackChannel
          <IDuplexRouterCallback>();

        DuplexChannelFactory<IDuplexRouterService> factory = 
          new DuplexChannelFactory<IDuplexRouterService>
          (new InstanceContext(null, 
          new DuplexRouterCallback(callback)), "serviceEndpoint");
        factory.Endpoint.Behaviors.Add(new MustUnderstandBehavior(false));
        this.m_duplexSession = factory.CreateChannel();
      }
    }

    this.m_duplexSession.ProcessMessage(requestMessage);
  }
  public void Dispose() {
    if (this.m_duplexSession != null) {
      try {
        ICommunicationObject obj = this.m_duplexSession as 
          ICommunicationObject;
        if (obj.State == CommunicationState.Faulted)
          obj.Abort();
        else
          obj.Close();
      }
      catch {}
    }
  }
}

public class DuplexRouterCallback : IDuplexRouterCallback {

  private IDuplexRouterCallback m_clientCallback;

  public DuplexRouterCallback(IDuplexRouterCallback clientCallback) {
    m_clientCallback = clientCallback;
  }

  public void ProcessMessage(Message requestMessage) {
    this.m_clientCallback.ProcessMessage(requestMessage);
  }
}
コールバック オブジェクトはコールバック コントラクトを実装し、サービスから応答を受信します。このコールバック オブジェクトは、クライアントのコールバック チャネルを使用して、クライアントに応答を返します。
ルーターのサービス インスタンス、クライアントのコールバック チャネル参照、およびルーターのコールバック チャネルはすべて、クライアントとのセッションの期間中ずっと存在しています。そのため、これがうまく機能するには、ルーターがセッションをサポートするエンドポイントを公開し、ダウンストリームのサービスがセッションをサポートする必要があります。

混在トランスポート セッション
状況によっては、ルーターが TCP 経由でアプリケーション サービスにメッセージを転送する一方で、クライアントは HTTP 経由でルーターにメッセージを送信する方が望ましい場合もあります。セキュリティ機能または信頼できるセッションが有効になっている場合は、二重ルーター構成でもこの状況をサポートするには不十分です。
前に述べたように、手動アドレス指定は要求/応答チャネルでのみサポートされています。それ以外の場合、サービス モデルはアドレス指定機能を使用してメッセージを相関付けます。TCP は要求/応答をネイティブではサポートしないため、コントラクトが一方向でない限り、手動アドレス指定はオプションとして選択できません。したがって、図 7 の送信チャネルは、IDuplexRouterService のような一方向コントラクトから作成する必要があります。コールバック チャネルは、応答を受信するために提供されます。
また、ルーターのコールバック チャネルは応答が送信されるまで保持し、クライアントのコールバック チャネルも同様に保持される必要があります。これをサポートするには、クライアントがルーターとのセッションを確立し、ルーターがサービスとのセッションを確立する必要があります。
ルーターによって呼び出されるアプリケーション サービスが安全であると仮定すると、ルーターで変更を加えずにメッセージを転送するには手動アドレス指定が必要になると考えられます。ルーターが TCP 経由でアプリケーション サービスを呼び出す場合には、前に述べたように二重ルーター実装が必要であるため、送信呼び出しは一方向チャネルとすることができます。これにより、クライアントは強制的にセッション対応のバインディングをとおしてメッセージを送信するようになり、これは安全なセッションまたは信頼できるセッションが HTTP 経由で有効になることを意味します。
ルーターがパススルー ルーターである場合、必要なことは、セキュリティおよび信頼できるセッションのヘッダーをアプリケーション サービスで処理させることです。HTTP 経由のセッションをサポートするために、ルーターがクライアント エンドポイントに対して安全なセッションまたは信頼できるセッションを必要とする場合は、ルーターがこれらのヘッダーを処理し、アプリケーション サービスとの間にセッションは確立されません。
したがって、チャネル層の内部にアクセスして既定の動作を上書きしない限り、プロトコルの混在は限定された状況でのみ機能します。セキュリティおよび信頼できるセッションが無効の場合、ルーターが TCP 経由でアプリケーション サービスにメッセージを転送する一方で、クライアントは HTTP 経由でルーターにメッセージを送信します。セキュリティまたは信頼できるセッションが有効である場合は、ルーター チャネルに対して信頼できるセッションや安全なセッションを有効にしないでセッションを確立できるようにするため、クライアントは TCP 経由でルーターにメッセージを送信する必要があります。

ご意見やご質問は、sstation@microsoft.com まで英語でお送りください。


Michele Leroux Bustamante は、IDesign Inc. の Chief Architect、サンディエゴ地域の Microsoft Regional Director、および Connected Systems の Microsoft MVP を兼任しています。彼女の最新の著書は『Learning WCF』です。連絡を取るには、mlb@idesign.net 宛てに電子メールを送るか、idesign.net にアクセスしてください。Michele のブログのアドレスは、dasblonde.net です。

Page view tracker