このページは役に立ちましたか。
このページのコンテンツについての ご意見をお待ちしております
その他にご意見はありますか。
残り 1500 文字
エクスポート (0) 印刷
すべて展開

WCF ワークフロー サービスを Service Bus のキューおよびトピックに統合する方法

更新日: 2015年3月

執筆者:Paolo Salvatori

校閲者:Ralph Squillace、Sidney Higa

このドキュメントは、WCF ワークフロー サービスを Microsoft Azure の Service Bus キューおよびトピックと統合する方法に関するガイダンスを提供します。段階的にトピックに従い、キューとトピックを使用してメッセージをクライアント アプリケーションと交換する WCF ワークフロー サービスを構築します。このガイドと一緒に、Roman Kiss の記事「Azure Service Bus メッセージングの使用」を参照してください。この記事では、同じ問題に対するさらに高度なソリューションや、WCF ワークフロー サービスで Service Bus のキューとトピックを操作するために使用できる再利用可能な一連の操作が紹介されています。

Microsoft Azure の Service Bus の詳細情報、次のリソースを参照してください。

次の図は、このデモのアーキテクチャの概略を示したものです。

ServiceBus-Queues-Topics-WCF-Workflow-Service4

  1. クライアント アプリケーションは、WCF プロキシと NetMessagingBinding を使用して、requestqueue または requesttopic に要求メッセージを送信します。

  2. コンソール アプリケーションまたは IIS で実行される WCF ワークフロー サービスは、requestqueue から、または requesttopic で定義された ItalyMilan サブスクリプションから要求メッセージを受信します。

    ServiceBus-Queues-Topics-WCF-Workflow-Service5
  3. WCF ワークフロー サービス (上記の図) は、次の操作を実行します。

    • カスタムの BrokeredMessagePropertyActivity (表示名は Get BrokeredMessage) は、着信メッセージから BrokeredMessageProperty を読み取り、その値を Sequential アクティビティの最も外側で定義されるワークフロー変数に割り当てます。

    • Receive アクティビティは、requestqueue から、または requesttopicItalyMilan サブスクリプションからメッセージを取得します。

    • カスタムの CalculatorActivity は、入力引数として着信メッセージと BrokeredMessageProperty を受け取り、その要求メッセージを処理して、応答メッセ���ジと発信 BrokeredMessageProperty を生成します。コンソール アプリケーションでの実行時、このアクティビティは、標準出力に含まれる着信および発信の BrokeredMessageProperty のプロパティをトレースします。

    • If アクティビティは、着信 BrokeredMessagePropertyReplyTo プロパティに格納されたアドレスを読み取ります。

      • 文字列に "topic" という単語が含まれている場合は、responsetopic に応答が送信されます。

      • それ以外の場合は、responsequeue に応答が送信されます。

    • どちらの場合も、BrokeredMessagePropertyActivity (表示名は Set BrokeredMessage) のインスタンスを使用して Send アクティビティがラップされ、発信 BrokeredMessageProperty が WCF 応答メッセージのプロパティ コレクションに割り当てられます。

  4. WCF ワークフロー サービスは、応答メッセージを responsequeue または responsetopic に書き込みます。

  5. クライアント アプリケーションは、WCF サービスと 2 つの別個のエンドポイントを使用して、responsequeue または responsetopic から応答メッセージを取得します。クライアント アプリケーションが複数ある環境で BizTalk から応答メッセージを受信する場合は、それぞれのクライアントで異なるキューまたはサブスクリプションを使用する必要があります。詳細については後ほど説明します。

デモの全体的なアーキテクチャについて説明したので、次はソリューションの主なコンポーネントについて詳しく見ていきましょう。

環境を正しく構成するために、まずはデモで使用するメッセージング エンティティを作成する必要があります。最初に、Service Bus の新しい名前空間をプロビジョニングするか、既存の名前空間を変更して Service Bus を含めます。これを行うには、Azure Management Portal[新規] ボタンまたは [変更] ボタンをクリックします。

次に、デモで必要なキュー、トピック、およびサブスクリプションの各エンティティを作成します。最初に説明したように、これを行う方法は複数あります。最も簡単なのは、Azure Management Portal[エンティティの管理] コマンドのボタンを使用する方法です。

ポイント 2 で表示されているナビゲーション ツリー ビューを使用して既存のエンティティを選択すると、ポイント 3 で強調表示されている垂直バーにそのプロパティが表示されます。選択したエンティティを削除するには、[エンティティの管理] コマンド バーで [削除] ボタンをクリックします。

noteメモ
特定の Service Bus 名前空間のメッセージング エンティティに関する操作を行うときは、Azure Management Portal を使用すると便利です。ただし、少なくとも現在のところ、開発者またはシステム管理者がそのユーザー インターフェイスで実行できる操作はかなり限られています。たとえば、Azure Management Portal では、キュー、トピック、およびサブスクリプションを作成してそれらのプロパティを定義することはできますが、既存のサブスクリプションのルールを作成したり表示したりすることはできません。これを行うには、現在は .NET メッセージング API を使用するしかありません。具体的には、既存のサブスクリプションに新しいルールを追加する場合は SubscriptionClient クラスで公開されている AddRule(String, Filter) メソッドまたは AddRule(RuleDescription) メソッドを使用し、既存のサブスクリプションのルールを列挙する場合は NamespaceManager クラスの GetRules メソッドを使用します。また、Azure Management Portal では、次の操作も実行できません。

  1. エンティティを階層形式で正しく表示する。Azure Management Portal では、キュー、トピック、およびサブスクリプションがフラットなツリー ビューで表示されます。ただし、メッセージング エンティティの名前を複数のパス セグメントで構成される絶対パス (例: crm/prod/queues/orderqueue) として指定するだけで、それらのエンティティを階層構造にまとめることができます。

  2. 特定の Service Bus 名前空間に含まれるメッセージング エンティティを XML バインド ファイル (BizTalk Server の形式) にエクスポートする。代わりに、Service Bus エクスプローラー ツールを使用すると、次のものを選択してエクスポートできます。

    1. 個々のエンティティ

    2. 種類別のエンティティ (キューまたはトピック)

    3. 特定のパスに含まれるエンティティ (例: crm/prod/queues)

    4. 特定の名前空間のすべてのエンティティ

  3. キュー、トピック、およびサブスクリプションを XML ファイルから既存の名前空間にインポートする。Service Bus エクスプローラーでは、XML ファイルにエンティティをエクスポートし、同一または別の Service Bus 名前空間に再インポートする機能がサポートされています。この機能は、名前空間のバックアップと復元を実行する場合や、テスト用の名前空間から実稼働用の名前空間にキューやトピックを転送する場合に便利です。

Service Bus エクスプローラーでは、キュー、トピック、サブスクリプション、およびルールの作成、削除、テストを行うことができます。このツールは、公式の Azure Management Portal と完全に連携します。

ここでは、便宜上、Provisioning というコンソール アプリケーションを作成してあります。このアプリケーションでは、NamespaceManager クラスで提供される機能を使用して、ソリューションに必要なキュー、トピック、およびサブスクリプションを作成します。このコンソール アプリケーションを起動すると、サービス名前空間の資格情報を入力するように求められます。この資格情報を使用してアクセス制御サービスで認証が行われ、新しいメッセージング エンティティをプロビジョニングすることが許可されたアプリケーションであることを Service Bus のインフラストラクチャに対して証明するアクセス トークンが取得されます。その後、作成するエンティティのプロパティに割り当てる値 (キューの EnabledBatchedOperationsEnableDeadLetteringOnMessageExpiration など) を指定するように求められます。Provisioning アプリケーションで、指定した Service Bus 名前空間に次のエンティティが作成されます。

  1. BizTalk Server に要求メッセージを送信するためにクライアント アプリケーションで使用される requestqueue というキュー。

  2. クライアント アプリケーションに応答メッセージを送信するために BizTalk Server で使用される responsequeue というキュー。

  3. BizTalk Server に要求メッセージを送信するためにクライアント アプリケーションで使用される requesttopic というトピック。

  4. クライアント アプリケーションに応答メッセージを送信するために BizTalk Server で使用される responsetopic というトピック。

  5. requesttopicItalyMilan というサブスクリプション。このトピックは、requesttopic から要求メッセージを受信するために BizTalk Server で使用されるトピックです。サブスクリプションには、次のように定義された 1 つのルールが含まれています。

    1. フィルター:Country='Italy' and City='Milan'

    2. 操作:Set Area='Western Europe'

  6. responsetopicItalyMilan というサブスクリプション。このトピックは、responsetopic から応答メッセージを受信するためにクライアント アプリケーションで使用されるトピックです。サブスクリプションには、次のように定義された 1 つのルールが含まれています。

    1. フィルター:Country='Italy' and City='Milan'

    2. 操作:Set Area='Western Europe'

次の図は、Provisioning コンソール アプリケーションの出力を示したものです。

ServiceBus-Queues-Topics-WCF-Workflow-Service8

次の表に、Provisioning アプリケーションのコードを示します。

#region Using Directives
using System;
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Messaging;
#endregion

namespace Microsoft.WindowsAzure.CAT.Samples.ServiceBus.Provisioning
{
    static class Program
    {
        #region Private Constants
        //***************************
        // Constants
        //***************************

        private const string RequestQueue = "requestqueue";
        private const string ResponseQueue = "responsequeue";
        private const string RequestTopic = "requesttopic";
        private const string ResponseTopic = "responsetopic";
        private const string RequestSubscription = "ItalyMilan";
        private const string ResponseSubscription = "ItalyMilan";
        #endregion
        static void Main()
        {
            var defaultColor = Console.ForegroundColor;

            try
            {
                // Set Window Size
                Console.WindowWidth = 100;
                Console.BufferWidth = 100;
                Console.WindowHeight = 48;
                Console.BufferHeight = 48;

                // Print Header            
                Console.WriteLine("Read Credentials:");
                Console.WriteLine("-----------------");

                // Read Service Bus Namespace
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Service Bus Namespace: ");
                Console.ForegroundColor = defaultColor;
                var serviceNamespace = Console.ReadLine();

                // Read Service Bus Issuer Name
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Service Bus Issuer Name: ");
                Console.ForegroundColor = defaultColor;
                var issuerName = “RootManageSharedAccessKey”;

                // Read Service Bus Issuer Secret
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Service Bus Issuer Secret: ");
                Console.ForegroundColor = defaultColor;
                var SASKey = Console.ReadLine();

                // Print Header
                Console.WriteLine();
                Console.WriteLine("Enter Queues Properties:");
                Console.WriteLine("------------------------");

                // Read Queue EnabledBatchedOperations
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Queues: ");
                Console.ForegroundColor = defaultColor;
                Console.Write("Set the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("EnabledBatchedOperations ");
                Console.ForegroundColor = defaultColor;
                Console.Write("to true [y=Yes, n=No]?");
                var key = Console.ReadKey().KeyChar;
                var queueEnabledBatchedOperations = key == 'y' || key == 'Y';
                Console.WriteLine();

                // Read Queue EnableDeadLetteringOnMessageExpiration
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Queues: ");
                Console.ForegroundColor = defaultColor;
                Console.Write("Set the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("EnableDeadLetteringOnMessageExpiration ");
                Console.ForegroundColor = defaultColor;
                Console.Write("to true [y=Yes, n=No]?");
                key = Console.ReadKey().KeyChar;
                var queueEnableDeadLetteringOnMessageExpiration = key == 'y' || key == 'Y';
                Console.WriteLine();

                // Read Queue RequiresDuplicateDetection
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Queues: ");
                Console.ForegroundColor = defaultColor;
                Console.Write("Set the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("RequiresDuplicateDetection ");
                Console.ForegroundColor = defaultColor;
                Console.Write("to true [y=Yes, n=No]?");
                key = Console.ReadKey().KeyChar;
                var queueRequiresDuplicateDetection = key == 'y' || key == 'Y';
                Console.WriteLine();

                // Read Queue RequiresSession
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Queues: ");
                Console.ForegroundColor = defaultColor;
                Console.Write("Set the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("RequiresSession ");
                Console.ForegroundColor = defaultColor;
                Console.Write("to true [y=Yes, n=No]?");
                key = Console.ReadKey().KeyChar;
                var queueRequiresSession = key == 'y' || key == 'Y';
                Console.WriteLine();

                // Print Header
                Console.WriteLine();
                Console.WriteLine("Enter Topic Properties:");
                Console.WriteLine("-----------------------");

                // Read Topic EnabledBatchedOperations
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Topics: ");
                Console.ForegroundColor = defaultColor;
                Console.Write("Set the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("EnabledBatchedOperations ");
                Console.ForegroundColor = defaultColor;
                Console.Write("to true [y=Yes, n=No]?");
                key = Console.ReadKey().KeyChar;
                var topicEnabledBatchedOperations = key == 'y' || key == 'Y';
                Console.WriteLine();

                // Read Topic RequiresDuplicateDetection
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Topics: ");
                Console.ForegroundColor = defaultColor;
                Console.Write("Set the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("RequiresDuplicateDetection ");
                Console.ForegroundColor = defaultColor;
                Console.Write("to true [y=Yes, n=No]?");
                key = Console.ReadKey().KeyChar;
                var topicRequiresDuplicateDetection = key == 'y' || key == 'Y';
                Console.WriteLine();

                // Print Header
                Console.WriteLine();
                Console.WriteLine("Enter Subscriptions Properties: ");
                Console.WriteLine("-------------------------------");

                // Read Subscription EnabledBatchedOperations
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Subscriptions: ");
                Console.ForegroundColor = defaultColor;
                Console.Write("Set the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("EnabledBatchedOperations ");
                Console.ForegroundColor = defaultColor;
                Console.Write("to true [y=Yes, n=No]?");
                key = Console.ReadKey().KeyChar;
                var subscriptionnabledBatchedOperations = key == 'y' || key == 'Y';
                Console.WriteLine();

                // Read Subscription EnableDeadLetteringOnFilterEvaluationExceptions
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Subscriptions: ");
                Console.ForegroundColor = defaultColor;
                Console.Write("Set the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("EnableDeadLetteringOnFilterEvaluationExceptions ");
                Console.ForegroundColor = defaultColor;
                Console.Write("to true [y=Yes, n=No]?");
                key = Console.ReadKey().KeyChar;
                var subscriptionEnableDeadLetteringOnFilterEvaluationExceptions = key == 'y' || key == 'Y';
                Console.WriteLine();

                // Read Subscription EnableDeadLetteringOnMessageExpiration
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Subscriptions: ");
                Console.ForegroundColor = defaultColor;
                Console.Write("Set the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("EnableDeadLetteringOnMessageExpiration ");
                Console.ForegroundColor = defaultColor;
                Console.Write("to true [y=Yes, n=No]?");
                key = Console.ReadKey().KeyChar;
                var subscriptionEnableDeadLetteringOnMessageExpiration = key == 'y' || key == 'Y';
                Console.WriteLine();

                // Read Subscription RequiresSession
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("Subscriptions: ");
                Console.ForegroundColor = defaultColor;
                Console.Write("Set the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("RequiresSession ");
                Console.ForegroundColor = defaultColor;
                Console.Write("to true [y=Yes, n=No]?");
                key = Console.ReadKey().KeyChar;
                var subscriptionRequiresSession = key == 'y' || key == 'Y';
                Console.WriteLine();

                // Get ServiceBusNamespaceClient for management operations
                var managementUri = 
                    ServiceBusEnvironment.CreateServiceUri("https", serviceNamespace, string.Empty);
                var tokenProvider = 
                    TokenProvider.CreateSharedAccessSignatureTokenProvider(issuerName, SASKey);
                var namespaceManager = new NamespaceManager(managementUri, tokenProvider);

                // Print Header
                Console.WriteLine();
                Console.WriteLine("Create Queues:");
                Console.WriteLine("--------------");

                // Create RequestQueue
                Console.Write("Creating ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(RequestQueue);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" queue...");
                if (namespaceManager.QueueExists(RequestQueue))
                {
                    namespaceManager.DeleteQueue(RequestQueue);
                }
                namespaceManager.CreateQueue(new QueueDescription(RequestQueue)
                                {
                                    EnableBatchedOperations = queueEnabledBatchedOperations,
                                    EnableDeadLetteringOnMessageExpiration =
                                        queueEnableDeadLetteringOnMessageExpiration,
                                    RequiresDuplicateDetection = queueRequiresDuplicateDetection,
                                    RequiresSession = queueRequiresSession
                                });
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(RequestQueue);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" queue successfully created.");

                // Create ResponseQueue
                Console.Write("Creating ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(ResponseQueue);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" queue...");
                if (namespaceManager.QueueExists(ResponseQueue))
                {
                    namespaceManager.DeleteQueue(ResponseQueue);
                }
                namespaceManager.CreateQueue(new QueueDescription(ResponseQueue)
                            {
                                EnableBatchedOperations = queueEnabledBatchedOperations,
                                EnableDeadLetteringOnMessageExpiration = 
                                    queueEnableDeadLetteringOnMessageExpiration,
                                RequiresDuplicateDetection = queueRequiresDuplicateDetection,
                                RequiresSession = queueRequiresSession
                            });
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(ResponseQueue);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" queue successfully created.");

                // Print Header
                Console.WriteLine();
                Console.WriteLine("Create Topics:");
                Console.WriteLine("--------------");

                // Create RequestTopic
                Console.Write("Creating ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(RequestTopic);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" topic...");
                if (namespaceManager.TopicExists(RequestTopic))
                {
                    namespaceManager.DeleteTopic(RequestTopic);
                }
                namespaceManager.CreateTopic(new TopicDescription(RequestTopic)
                                        {
                                            EnableBatchedOperations = topicEnabledBatchedOperations,
                                            RequiresDuplicateDetection = topicRequiresDuplicateDetection
                                        });
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(RequestTopic);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" topic successfully created.");

                // Create ResponseTopic
                Console.Write("Creating ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(ResponseTopic);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" topic...");
                if (namespaceManager.TopicExists(ResponseTopic))
                {
                    namespaceManager.DeleteTopic(ResponseTopic);
                }
                namespaceManager.CreateTopic(new TopicDescription(ResponseTopic)
                                    {
                                        EnableBatchedOperations = topicEnabledBatchedOperations,
                                        RequiresDuplicateDetection = topicRequiresDuplicateDetection
                                    });
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(ResponseTopic);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" topic successfully created.");

                // Print Header
                Console.WriteLine();
                Console.WriteLine("Create Subscriptions:");
                Console.WriteLine("--------------");

                // Create Request Subscription
                Console.Write("Creating ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(RequestSubscription);
                Console.ForegroundColor = defaultColor;
                Console.Write(" subscription for the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(RequestTopic);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" topic...");
                var ruleDescription = new RuleDescription(new SqlFilter("Country='Italy' and City='Milan'"))
                                          {
                                              Name = "$Default",
                                              Action = new SqlRuleAction("Set Area='Western Europe'")
                                          };
                var subscriptionDescription = new SubscriptionDescription(RequestTopic, RequestSubscription)
                {
                    EnableBatchedOperations = subscriptionnabledBatchedOperations,
                    EnableDeadLetteringOnFilterEvaluationExceptions = 
                            subscriptionEnableDeadLetteringOnFilterEvaluationExceptions,
                    EnableDeadLetteringOnMessageExpiration = 
                            subscriptionEnableDeadLetteringOnMessageExpiration,
                    RequiresSession = subscriptionRequiresSession
                };
                namespaceManager.CreateSubscription(subscriptionDescription, ruleDescription);
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(RequestSubscription);
                Console.ForegroundColor = defaultColor;
                Console.Write(" subscription for the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(RequestTopic);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" topic successfully created.");

                // Create Response Subscription
                Console.Write("Creating ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(ResponseSubscription);
                Console.ForegroundColor = defaultColor;
                Console.Write(" subscription for the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(ResponseTopic);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" topic...");
                ruleDescription = new RuleDescription(new SqlFilter("Country='Italy' and City='Milan'"))
                                      {
                                          Action = new SqlRuleAction("Set Area='Western Europe'")
                                      };
                subscriptionDescription = new SubscriptionDescription(ResponseTopic, ResponseSubscription)
                {
                    EnableBatchedOperations = subscriptionnabledBatchedOperations,
                    EnableDeadLetteringOnFilterEvaluationExceptions = 
                            subscriptionEnableDeadLetteringOnFilterEvaluationExceptions,
                    EnableDeadLetteringOnMessageExpiration = 
                            subscriptionEnableDeadLetteringOnMessageExpiration,
                    RequiresSession = subscriptionRequiresSession
                };
                namespaceManager.CreateSubscription(subscriptionDescription, ruleDescription);
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(ResponseSubscription);
                Console.ForegroundColor = defaultColor;
                Console.Write(" subscription for the ");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write(ResponseTopic);
                Console.ForegroundColor = defaultColor;
                Console.WriteLine(" topic successfully created.");
                Console.WriteLine();

                // Close the application
                Console.WriteLine("Press any key to continue ...");
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("Exception: ");
                Console.ForegroundColor = defaultColor;
                Console.Write(ex.Message);
            }
        }
    }
}

noteメモ
キューおよびトピックのプロパティについて、有効なすべての組み合わせをテストしたわけではないため、構成によってはデモが想定どおりに動作しない可能性があります。

最初に、要求メッセージと応答メッセージのデータ コントラクトを定義しました。データ コントラクトは、コードで定義した .NET CLR の型と W3C (www.w3c.org) で定義されている XML スキーマ (XSD) をマップするメカニズムです。データ コントラクトはサービスのメタデータでパブリッシュされるため、クライアント側で、テクノロジに依存しない共通の形式で記述されたデータ型をネイティブ形式に変換することができます。データ コントラクトの詳細については、次の資料を参照してください。

このソリューションでは、DataContracts というプロジェクトを作成して、要求メッセージと応答メッセージを表すクラスを定義しています。便宜上、以下のコードを含めてあります。

CalculatorRequest クラス

[Serializable]
[XmlType(TypeName = "CalculatorRequest", 
         Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
[XmlRoot(ElementName = "CalculatorRequest", 
         Namespace = http://windowsazure.cat.microsoft.com/samples/servicebus, 
         IsNullable = false)]
[DataContract(Name = "CalculatorRequest", 
              Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
public class CalculatorRequest
{
    #region Private Fields
    private OperationList operationList;
    #endregion

    #region Public Constructors
    public CalculatorRequest()
    {
        operationList = new OperationList();
    }

    public CalculatorRequest(OperationList operationList)
    {
        this.operationList = operationList;
    }
    #endregion

    #region Public Properties
    [XmlArrayItem("Operation", Type=typeof(Operation), IsNullable = false)]
    [DataMember(Order = 1)]
    public OperationList Operations
    {
        get
        {
            return operationList;
        }
        set
        {
            operationList = value;
        }
    } 
    #endregion
}

[CollectionDataContract(Name = "OperationList", 
                        Namespace = http://windowsazure.cat.microsoft.com/samples/servicebus, 
                        ItemName = "Operation")]
public class OperationList : List<Operation>
{
}

[Serializable]
[XmlType(TypeName = "Operation", 
         AnonymousType = true, 
         Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
[DataContract(Name = "Operation", 
              Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
public class Operation
{
    #region Private Fields
    private string op;
    private double operand1;
    private double operand2;
    #endregion

    #region Public Constructors
    public Operation()
    {
    }

    public Operation(string op,
                        double operand1,
                        double operand2)
    {
        this.op = op;
        this.operand1 = operand1;
        this.operand2 = operand2;
    }
    #endregion

    #region Public Properties
    [XmlElement]
    [DataMember(Order = 1)]
    public string Operator
    {
        get
        {
            return op;
        }
        set
        {
            op = value;
        }
    }

    [XmlElement]
    [DataMember(Order = 2)]
    public double Operand1
    {
        get
        {
            return operand1;
        }
        set
        {
            operand1 = value;
        }
    }

    [XmlElement]
    [DataMember(Order = 3)]
    public double Operand2
    {
        get
        {
            return operand2;
        }
        set
        {
            operand2 = value;
        }
    } 
    #endregion
}

CalculatorResponse クラス

[Serializable]
[XmlType(TypeName = "CalculatorResponse", 
         Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
[XmlRoot(ElementName = "CalculatorResponse", 
         Namespace = http://windowsazure.cat.microsoft.com/samples/servicebus, 
         IsNullable = false)]
[DataContract(Name = "CalculatorResponse", 
         Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
public class CalculatorResponse
{
    #region Private Fields
    private string status;
    private ResultList resultList;
    #endregion

    #region Public Constructors
    public CalculatorResponse()
    {
        status = default(string);
        resultList = new ResultList();
    }

    public CalculatorResponse(string status)
    {
        this.status = status;
        resultList = new ResultList();
    }

    public CalculatorResponse(string status, ResultList resultList)
    {
        this.status = status;
        this.resultList = resultList;
    }
    #endregion

    #region Public Properties
    [XmlElement]
    [DataMember(Order = 1)]
    public string Status 
    {
        get 
        {
            return status;
        }
        set 
        {
            status = value;
        }
    }

    [XmlArrayItem("Result", Type=typeof(Result), IsNullable=false)]
    [DataMember(Order = 2)]
    public ResultList Results 
    {
        get 
        {
            return resultList;
        }
        set 
        {
            resultList = value;
        }
    }
    #endregion
}

[CollectionDataContract(Name = "ResultList", 
                        Namespace = http://windowsazure.cat.microsoft.com/samples/servicebus, 
                        ItemName = "Result")]
public class ResultList : List<Result>
{
}

[Serializable]
[XmlType(TypeName = "Result", 
         AnonymousType = true, 
         Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
[DataContract(Name = "Result", 
              Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus")]
public class Result
{
    #region Private Fields
    private double value;
    private string error; 
    #endregion

    #region Public Constructors
    public Result()
    {
        value = default(double);
        error = default(string);
    }

    public Result(double value, string error)
    {
        this.value = value;
        this.error = error;
    }
    #endregion

    #region Public Properties
    [XmlElement]
    [DataMember(Order = 1)]
    public double Value
    {
        get
        {
            return value;
        }
        set
        {
            this.value = value;
        }
    }

    [XmlElement]
    [DataMember(Order = 2)]
    public string Error
    {
        get
        {
            return error;
        }
        set
        {
            error = value;
        }
    } 
    #endregion
}

次に、クライアント アプリケーションで Service Bus とのメッセージの交換に使用するサービス コントラクトを定義しました。このために、ソリューションで ServiceContracts という新しいプロジェクトを作成し、クライアント アプリケーションで Service Bus のメッセージング エンティティとの間のメッセージの送信と受信にそれぞれ使用する 2 つのサービス コントラクト インターフェイスを定義しています。実際には、応答メッセージの受信に使用するサービス コントラクトは 2 種類作成してあります。

  • ICalculatorResponse インターフェイスは、セッションフルでないキューまたはサブスクリプションからの応答メッセージの受信に使用します。

  • ICalculatorResponseSessionful インターフェイスは、ICalculatorResponse サービス コントラクトを継承し、[ServiceContract(SessionMode = SessionMode.Required)] 属性を設定したものです。このサービス コントラクトは、セッションフルでないキューまたはサブスクリプションからの応答メッセージの受信に使用します。

どのコントラクトで定義されるメソッドも一方向であることに注意してください。これは必須の要件です。

ICalculatorRequest インターフェイス、ICalculatorResponse インターフェイス、ICalculatorResponseSessionful インターフェイス

#region Using Directives
using System.ServiceModel;
using Microsoft.WindowsAzure.CAT.Samples.ServiceBus.MessageContracts;
#endregion

namespace Microsoft.WindowsAzure.CAT.Samples.ServiceBus.ServiceContracts
{
    [ServiceContract(Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus",
                     ConfigurationName = "ICalculatorRequest", 
                     SessionMode = SessionMode.Allowed)]
    public interface ICalculatorRequest
    {
        [OperationContract(Action = "SendRequest", IsOneWay = true)]
        [ReceiveContextEnabled(ManualControl = true)]
        void SendRequest(CalculatorRequestMessage calculatorRequestMessage);
    }

    [ServiceContract(Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus", 
                     ConfigurationName = "ICalculatorResponse",
                     SessionMode = SessionMode.Allowed)]
    public interface ICalculatorResponse
    {
        [OperationContract(Action = "ReceiveResponse", IsOneWay = true)]
        [ReceiveContextEnabled(ManualControl = true)]
        void ReceiveResponse(CalculatorResponseMessage calculatorResponseMessage);
    }

    [ServiceContract(Namespace = "http://windowsazure.cat.microsoft.com/samples/servicebus", 
                     ConfigurationName = "ICalculatorResponse"
                     SessionMode = SessionMode.Required)]
    public interface ICalculatorResponseSessionful : ICalculatorResponse
    {
    }
}

これで準備は完了です。次は、クライアント アプリケーションのコードについて見ていきましょう。

Windows フォーム アプリケーションは、基になる WCF ワークフロー サービスとの間のメッセージの交換を Service Bus のメッセージング エンティティを通じて非同期に行うため、クライアントとサービス アプリケーションの両方の役割を果たします。Windows フォームでは、WCF と NetMessagingBinding を使用して次の操作を実行します。

  1. 要求メッセージを requestqueue に送信する。

  2. 要求メッセージを requesttopic に送信する。

  3. responsequeue から応答メッセージを受信する。

  4. responsetopicItalyMilan サブスクリプションから応答メッセージを受信する。

まず、クライアント アプリケーションの構成ファイルから見ていきましょう。このファイルは、Service Bus との通信に使用される WCF クライアントと WCF サービスのエンドポイントを定義する中心的なファイルです。

クライアント アプリケーションの App.Config


   1:  <?xml version="1.0"?>
   2:  <configuration>
   3:    <system.diagnostics>
   4:      <sources>
   5:        <source name="System.ServiceModel.MessageLogging"  
                     switchValue="Warning, ActivityTracing">
   6:          <listeners>
   7:            <add type="System.Diagnostics.DefaultTraceListener" 
                      name="Default">
   8:              <filter type="" />
   9:            </add>
  10:            <add name="ServiceModelMessageLoggingListener">
  11:              <filter type="" />
  12:            </add>
  13:          </listeners>
  14:        </source>
  15:      </sources>
  16:      <sharedListeners>
  17:        <add initializeData="C:\ServiceBusWFClient.svclog"
  18:             type="System.Diagnostics.XmlWriterTraceListener, System, 
  19:                   Version=2.0.0.0, Culture=neutral,
                        PublicKeyToken=b77a5c561934e089"
  20:             name="ServiceModelMessageLoggingListener"
  21:             traceOutputOptions="Timestamp">
  22:          <filter type="" />
  23:        </add>
  24:      </sharedListeners>
  25:      <trace autoflush="true" indentsize="4">
  26:        <listeners>
  27:          <clear/>
  28:          <add name="LogTraceListener"
  29:               type="Microsoft.WindowsAzure.CAT.Samples.ServiceBusAndWF.Client.LogTraceListener, Client"
  30:               initializeData="" />
  31:        </listeners>
  32:      </trace>
  33:    </system.diagnostics>
  34:    <startup>
  35:     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  36:    </startup>
  37:    <system.serviceModel>
  38:      <diagnostics>
  39:        <messageLogging logEntireMessage="true"
  40:                        logMalformedMessages="false"
  41:                        logMessagesAtServiceLevel="true"
  42:                        logMessagesAtTransportLevel="false" />
  43:      </diagnostics>
  44:      <behaviors>
  45:        <endpointBehaviors>
  46:          <behavior name="securityBehavior">
  47:            <transportClientEndpointBehavior>
  48:              <tokenProvider>
  49:                <sharedAccessSignature keyName="RootManageSharedAccessKey" key="SASKey" />
  51:              </tokenProvider>
  52:            </transportClientEndpointBehavior>
  53:          </behavior>
  54:        </endpointBehaviors>
  55:      </behaviors>
  56:      <bindings>
  57:        <netMessagingBinding>
  58:          <binding name="netMessagingBinding"
  59:                   sendTimeout="00:03:00"
  60:                   receiveTimeout="00:03:00"
  61:                   openTimeout="00:03:00"
  62:                   closeTimeout="00:03:00"
  63:                   sessionIdleTimeout="00:01:00"
  64:                   prefetchCount="-1">
  65:            <transportSettings batchFlushInterval="00:00:01" />
  66:          </binding>
  67:        </netMessagingBinding>
  68:      </bindings>
  69:      <client>
  70:        <!-- Invoke WF Service via Service Bus Queue -->
  71: <endpoint address="sb://NAMESPACE.servicebus.windows.net/requestqueue"
  72:                  behaviorConfiguration="securityBehavior" 
  73:                  binding="netMessagingBinding"
  74:                  bindingConfiguration="netMessagingBinding" 
  75:                  contract="ICalculatorRequest"
  76:                  name="requestQueueClientEndpoint" />
  77:        <!-- Invoke WF Service via Service Bus Topic -->
  78: <endpoint address="sb://NAMESPACE.servicebus.windows.net/requesttopic"
  79:                  behaviorConfiguration="securityBehavior"
  80:                  binding="netMessagingBinding"
  81:                  bindingConfiguration="netMessagingBinding"
  82:                  contract="ICalculatorRequest"
  83:                  name="requestTopicClientEndpoint" />
  84:      </client>
  85:      <services>
  86:        <service name="ResponseHandlerService">
  87: <endpoint address="sb://NAMESPACE.servicebus.windows.net/responsequeue"
  88:                    behaviorConfiguration="securityBehavior"
  89:                    binding="netMessagingBinding"
  90:                    bindingConfiguration="netMessagingBinding"
  91:                    name="responseQueueServiceEndpoint"
  92:                    contract="ICalculatorResponse" />
  93: <endpoint address="sb://NAMESPACE.servicebus.windows.net/responsetopic"
  94:                    listenUri="sb://NAMESPACE.servicebus.windows.net/responsetopic/Subscriptions/ItalyMilan"
  95:                    behaviorConfiguration="securityBehavior"
  96:                    binding="netMessagingBinding"
  97:                    bindingConfiguration="netMessagingBinding"
  98:                    name="responseSubscriptionServiceEndpoint"
  99:                    contract="ICalculatorResponse" />
 100:        </service>
 101:      </services>
 102:    </system.serviceModel>
 103:  </configuration>

構成ファイルの主な要素とセクションの簡単な説明を以下に示します。

  • [3-32] 行目では、LogTraceListener というカスタムのトレース リスナーを定義しています。これは、Windows フォーム アプリケーションのログ コントロールに応答メッセージを書き込むために ResponseHandlerService で使用されます。

  • [34-36] 行目の startup セクションでは、アプリケーションでサポートされる共通言語ランタイムのバージョンを指定しています。

  • [46-53] 行目では、securityBehavior を定義しています。これは、アクセス制御サービスでの認証にクライアントとサービスのエンドポイントで使用されます。具体的には、TransportClientEndpointBehavior を使用して、共有シークレットの資格情報を定義しています。Azure Management Portal から資格情報を取得する方法の詳細については、この後の注を参照してください。

  • [57-67] 行目では、NetMessagingBinding を構成しています。これは、Service Bus とメッセージを交換するためにクライアントとサービスのエンドポイントで使用されます。

  • [71-76] 行目では、requestQueueClientEndpoint を定義しています。これは、requestqueue に要求メッセージを送信するためにアプリケーションで使用されます。クライアントのエンドポイントの address は、サービス名前空間の URL とキューの名前を連結したものに��ります。

  • [78-83] 行目では、requestTopicClientEndpoint を定義しています。これは、requesttopic に要求メッセージを送信するためにアプリケーションで使用されます。クライアントのエンドポイントの address は、サービス名前空間の URL とトピックの名前を連結したものになります。

  • [87-92] 行目では、responseQueueServiceEndpoint を定義しています。これは、responsequeue から応答メッセージを受信するためにアプリケーションで使用されます。サービスのエンドポイントの address は、サービス名前空間の URL とキューの名前を連結したものになります。

  • [93-99] 行目では、responseSubscriptionServiceEndpoint を定義しています。これは、responsetopicItalyMilan サブスクリプションから応答メッセージを受信するためにアプリケーションで使用されます。NetMessagingBinding を使用してサブスクリプションからメッセージを受信する WCF サービスのエンドポイントを定義するときは、次の手順に従う必要があります (詳細については、この後の注を参照してください)。

    • address 属性の値には、サブスクリプションが属しているトピックの URL を指定する必要があります。トピックの URL は、サービス名前空間の URL とトピックの名前を連結したものになります。

    • listenUri 属性の値には、サブスクリプションの URL を指定する必要があります。サブスクリプションの URL は、トピックの URL、文字列 /Subscriptions/、およびサブスクリプションの名前を連結したものになります。

    • listenUriMode 属性には、値 Explicit を割り当てます。listenUriMode は、既定値が Explicit であるため、この設定は省略してもかまいません。

noteメモ
セッションフルなキューまたはサブスクリプションからメッセージを受け取るように WCF サービスのエンドポイントを構成するときは、サービス コントラクトでセッションをサポートする必要があります。そのため、このサンプルでセッションフルなキューまたはサブスクリプションからメッセージを受信する場合は、responseQueueServiceEndpoint エンドポイントまたは responseSubscriptionServiceEndpoint エンドポイントの構成で、ICalculatorResponse サービス コントラクトをセッションフルな ICalculatorResponseSessionful コントラクト インターフェイスに置き換える必要があります。詳細については、「サービス コントラクト」を参照してください。

noteメモ
Service Bus は次の 3 種類の資格情報形式をサポートしています。SAML共有シークレットSimple Web Token をサポートしますが、このバージョンの Service Bus エクスプローラー共有シークレット資格情報だけをサポートします。ただし、このコードは他の資格情報の形式をサポートするように簡単に拡張できます。Azure Management Portal から発行者のシークレット キーを取得するには、[Service Bus] セクションで特定の名前空間を選択してから [表示] ボタンをクリックします。

表示されたモーダル ダイアログで、赤色で強調表示された [クリップボードにコピー] をクリックするとキーを取得できます。

noteメモ
通常、既定の発行者の名前は常に owner になります。

noteメモ
NetMessagingBinding を使用してサブスクリプションからメッセージを受信する WCF サービスのエンドポイントを定義するときに、サービスのエンドポイントの address 属性に誤ってサブスクリプションの URL を割り当てると (この後の構成を参照)、実行時に次のような FaultException が発生します。

The message with To 'sb://NAMESPACE.servicebus.windows.net/responsetopic' cannot be processed at 
the receiver, due to an AddressFilter mismatch at the EndpointDispatcher. 
Check that the sender and receiver's EndpointAddresses agree."}

誤った構成


<?xml version="1.0"?>
<configuration>
  ...
  <system.serviceModel>
    ...
    <services>
      <service name="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.Service.ResponseHandlerService">
        <endpoint address="sb://NAMESPACE.servicebus.windows.net/responsetopic/Subscriptions/ItalyMilan"
                  behaviorConfiguration="securityBehavior"
                  binding="netMessagingBinding"
                  bindingConfiguration="netMessagingBinding"
                  name="responseSubscriptionServiceEndpoint"
                  contract="ICalculatorResponse" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

このエラーは、メッセージの WS-Addressing の To ヘッダーに、サブスクリプションのアドレスではなく、トピックのアドレスが指定されていることが原因で発生します。


<:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">ReceiveResponse</a:Action>
    <a:MessageID>urn:uuid:64cb0b06-1622-4920-a035-c27b610cfcaf</a:MessageID>
    <a:To s:mustUnderstand="1">sb://NAMESPACE.servicebus.windows.net/responsetopic</a:To>
  </s:Header>
  <s:Body>... stream ...</s:Body>
</s:Envelope>

サブスクリプションからメッセージを受信するようにサービスのエンドポイントを正しく構成するには、次の手順に従う必要があります。

  • address 属性の値には、サブスクリプションが属しているトピックの URL を指定する必要があります。トピックの URL は、サービス名前空間の URL とトピックの名前を連結したものになります。

  • listenUri 属性の値には、サブスクリプションの URL を指定する必要があります。サブスクリプションの URL は、トピックの URL、文字列 /Subscriptions/、およびサブスクリプションの名前を連結したものになります。

  • listenUriMode 属性には、値 Explicit を割り当てます。listenUriMode は、既定値が Explicit であるため、この設定は省略してもかまいません。

addresslistenUri、および listenUriMode の属性については、MSDN の該当するページを参照してください。

正しい構成


<?xml version="1.0"?>
<configuration>
  ...
  <system.serviceModel>
    ...
    <services>
      <service name="Microsoft.WindowsAzure.CAT.Samples.ServiceBus.Service.ResponseHandlerService">
        <endpoint address="sb://NAMESPACE.servicebus.windows.net/responsetopic"
                  listenUri="sb://NAMESPACE.servicebus.windows.net/responsetopic/Subscriptions/ItalyMilan"
                  behaviorConfiguration="securityBehavior"
                  binding="netMessagingBinding"
                  bindingConfiguration="netMessagingBinding"
                  name="subscriptionEndpoint"
                  contract="ICalculatorResponse" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

同じ作業を API で行うには、ServiceEndpoint インスタンスの AddressListenUri、および ListenUriMode のプロパティの値を、この注の説明に従って適切に設定する必要があります。

次の表に、responsequeue から、および responsetopicItalyMilan サブスクリプションからメッセージを読み取るための ResponseHandlerService を開始するためにクライアント アプリケーションで使用されるコードを示します。次のセクションで、このサービスのコードについて説明します。


private void StartServiceHost()
{
    try
    {
        // Creating the service host object as defined in config
        var serviceHost = new ServiceHost(typeof(ResponseHandlerService));
                
        // Add ErrorServiceBehavior for handling errors encounter by servicehost during execution.
        serviceHost.Description.Behaviors.Add(new ErrorServiceBehavior());


        foreach (var serviceEndpoint in serviceHost.Description.Endpoints)
        {
            if (serviceEndpoint.Name == "responseQueueServiceEndpoint")
            {
                responseQueueUri = serviceEndpoint.Address.Uri.AbsoluteUri;
                WriteToLog(string.Format(ServiceHostListeningOnQueue,
                                        serviceEndpoint.Address.Uri.AbsoluteUri));
            }
            if (serviceEndpoint.Name == "responseSubscriptionServiceEndpoint")
            {
                responseTopicUri = serviceEndpoint.Address.Uri.AbsoluteUri;
                WriteToLog(string.Format(ServiceHostListeningOnSubscription,
                                            serviceEndpoint.ListenUri.AbsoluteUri));
            }
        }

        // Start the service host
        serviceHost.Open();
        WriteToLog(ServiceHostSuccessfullyOpened);
    }
    catch (Exception ex)
    {
        mainForm.HandleException(ex);
    }
}

次の図は、クライアント アプリケーションのユーザー インターフェイスを示しています。

ServiceBus-Queues-Topics-WCF-Workflow-Service11

[Request Method] グループのオプション ボタンで、要求メッセージを requestqueue に送信するか requesttopic に送信するかを選択できます。また、[Response Method] グループのオプション ボタンで、要求を responsequeue から受信するか responsetopicItalyMilan サブスクリプションから受信するかを選択できます。この選択内容を基になる BizTalk アプリケーションに伝えるために、BrokeredMessageProperty オブジェクトを使用して、responseQueueUri または responseTopicUri のプライベート フィールドの値を ReplyTo プロパティに割り当てています。次の表に、Service Bus にメッセージを送信するためにクライアント アプリケーションで使用されるメソッドのコードを示します。便宜上、コードの内容を理解しやすいようにコメントを追加してあります。


private void SendRequestMessageUsingWCF(string endpointConfigurationName)
{
    try
    {
        if (string.IsNullOrEmpty(endpointConfigurationName))
        {
            WriteToLog(EndpointConfigurationNameCannotBeNull);
            return;
        }

        // Set the wait cursor
        Cursor = Cursors.WaitCursor;

        // Make sure that the request message contains at least an operation
        if (operationList == null ||
            operationList.Count == 0)
        {
            WriteToLog(OperationListCannotBeNull);
            return;
        }

        // Create warning collection
        var warningCollection = new List<string>();

        // Create request message
        var calculatorRequest = new CalculatorRequest(operationList);
        var calculatorRequestMessage = new CalculatorRequestMessage(calculatorRequest);

        // Create the channel factory for the currennt client endpoint
        // and cache it in the channelFactoryDictionary
        if (!channelFactoryDictionary.ContainsKey(endpointConfigurationName))
        {
            channelFactoryDictionary[endpointConfigurationName] = 
                new ChannelFactory<ICalculatorRequest>(endpointConfigurationName);
        }

        // Create the channel for the currennt client endpoint
        // and cache it in the channelDictionary
        if (!channelDictionary.ContainsKey(endpointConfigurationName))
        {
            channelDictionary[endpointConfigurationName] = 
                channelFactoryDictionary[endpointConfigurationName].CreateChannel();
        }

        // Use the OperationContextScope to create a block within which to access the current OperationScope
        using (new OperationContextScope((IContextChannel)channelDictionary[endpointConfigurationName]))
        {
            // Create a new BrokeredMessageProperty object
            var brokeredMessageProperty = new BrokeredMessageProperty();

            // Read the user defined properties and add them to the  
            // Properties collection of the BrokeredMessageProperty object
            foreach (var e in propertiesBindingSource.Cast<PropertyInfo>())
            {
                try
                {
                    e.Key = e.Key.Trim();
                    if (e.Type != StringType && e.Value == null)
                    {
                        warningCollection.Add(string.Format(CultureInfo.CurrentUICulture, 
                                                            PropertyValueCannotBeNull, e.Key));
                    }
                    else
                    {
                        if (brokeredMessageProperty.Properties.ContainsKey(e.Key))
                        {
                            brokeredMessageProperty.Properties[e.Key] = 
                                ConversionHelper.MapStringTypeToCLRType(e.Type, e.Value);
                        }
                        else
                        {
                            brokeredMessageProperty.Properties.Add(e.Key, 
                                ConversionHelper.MapStringTypeToCLRType(e.Type, e.Value));
                        }
                    }
                }
                catch (Exception ex)
                {
                    warningCollection.Add(string.Format(CultureInfo.CurrentUICulture, 
                        PropertyConversionError, e.Key, ex.Message));
                }
            }

            // if the warning collection contains at least one or more items,
            // write them to the log and return immediately
            StringBuilder builder;
            if (warningCollection.Count > 0)
            {
                builder = new StringBuilder(WarningHeader);
                var warnings = warningCollection.ToArray<string>();
                for (var i = 0; i < warningCollection.Count; i++)
                {
                    builder.AppendFormat(WarningFormat, warnings[i]);
                }
                mainForm.WriteToLog(builder.ToString());
                return;
            }

            // Set the BrokeredMessageProperty properties
            brokeredMessageProperty.Label = txtLabel.Text;
            brokeredMessageProperty.MessageId = Guid.NewGuid().ToString();
            brokeredMessageProperty.SessionId = sessionId;
            brokeredMessageProperty.ReplyToSessionId = sessionId;
            brokeredMessageProperty.ReplyTo = responseQueueRadioButton.Checked
                                                ? responseQueueUri
                                                : responseTopicUri;
            OperationContext.Current.OutgoingMessageProperties.Add(BrokeredMessageProperty.Name, 
                                                                    brokeredMessageProperty);
                    
            // Send the request message to the requestqueue or requesttopic
            var stopwatch = new Stopwatch();
            try
            {
                stopwatch.Start();
                channelDictionary[endpointConfigurationName].SendRequest(calculatorRequestMessage);
            }
            catch (CommunicationException ex)
            {
                if (channelFactoryDictionary[endpointConfigurationName] != null)
                {
                    channelFactoryDictionary[endpointConfigurationName].Abort();
                    channelFactoryDictionary.Remove(endpointConfigurationName);
                    channelDictionary.Remove(endpointConfigurationName);
                }
                HandleException(ex);
            }
            catch (Exception ex)
            {
                if (channelFactoryDictionary[endpointConfigurationName] != null)
                {
                    channelFactoryDictionary[endpointConfigurationName].Abort();
                    channelFactoryDictionary.Remove(endpointConfigurationName);
                    channelDictionary.Remove(endpointConfigurationName);
                }
                HandleException(ex);
            }
            finally
            {
                stopwatch.Stop();
            }
            // Log the request message and its properties
            builder = new StringBuilder();
            builder.AppendLine(string.Format(CultureInfo.CurrentCulture,
                    MessageSuccessfullySent,
                    channelFactoryDictionary[endpointConfigurationName].Endpoint.Address.Uri.AbsoluteUri,
                    brokeredMessageProperty.MessageId,
                    brokeredMessageProperty.SessionId,
                    brokeredMessageProperty.Label,
                    stopwatch.ElapsedMilliseconds));
            builder.AppendLine(PayloadFormat);
            for (var i = 0; i < calculatorRequest.Operations.Count; i++)
            {
                builder.AppendLine(string.Format(RequestFormat,
                                                    i + 1,
                                                    calculatorRequest.Operations[i].Operand1,
                                                    calculatorRequest.Operations[i].Operator,
                                                    calculatorRequest.Operations[i].Operand2));
            }
            builder.AppendLine(SentMessagePropertiesHeader);
            foreach (var p in brokeredMessageProperty.Properties)
            {
                builder.AppendLine(string.Format(MessagePropertyFormat,
                                                    p.Key,
                                                    p.Value));
            }
            var traceMessage = builder.ToString();
            WriteToLog(traceMessage.Substring(0, traceMessage.Length - 1));
        }
    }
    catch (Exception ex)
    {
        // Handle the exception
        HandleException(ex);
    }
    finally
    {
        // Restoire the defaulf cursor
        Cursor = Cursors.Default;
    }
}

次の表に、responsequeue から、および responsetopicItalyMilan サブスクリプションから応答メッセージを取得して記録するためにクライアント アプリケーションで使用される WCF サービスのコードを示します。これを実現するために、このサービスでは、それぞれ NetMessagingBinding を使用して 2 つのキューのうちの一方からメッセージを受信する 2 つのエンドポイントを公開しています。実際、各サブスクリプションは、それらが属するトピックにパブリッシュされたメッセージのコピーを取得する仮想キューと見なすことができます。次の表は ResponseHandlerService クラスのコードを示しています。コードからわかるように、このサービスでは、着信 WCF メッセージProperties コレクションから BrokeredMessageProperty を取得し、このオブジェクトを使用して応答メッセージのプロパティにアクセスします。ICalculatorResponse サービス コントラクトで ReceiveResponse メソッドが [ReceiveContextEnabled(ManualControl = true)] で修飾されているため、サービス メソッドで明示的に受信確認のシグナルを送る必要があります。この場合、サービスで明示的に ReceiveContext.Complete メソッドを呼び出して受信操作をコミットする必要があります。実際、この記事で最初に説明したように、ManualControl プロパティが true に設定されている場合、チャネルから受信するメッセージはロックが適用された状態でサービス操作に配信されます。そのため、メッセージの受信が完了したことを示すシグナルを送るために、サービスの実装で Complete(TimeSpan) または Abandon(TimeSpan) を呼び出す必要があります。これらのどちらかを呼び出さないと、ロックがタイムアウトするまでメッセージのロックが保持されます。ロックが解除されると (Abandon(TimeSpan) を呼び出すかロックがタイムアウトすると)、メッセージがチャネルからサービスに再度ディスパッチされます。Complete(TimeSpan) を呼び出した場合は、メッセージの受信が正常に完了したことを示すマークが付けられます。

ResponseHandlerService クラス


      [ServiceBehavir(Namespace = "http://windwsazure.cat.micrsft.cm/samples/servicebus", 
                 CnfiguratinName = "RespnseHandlerService")]
 public class RespnseHandlerService : ICalculatrRespnseSessinful
 {
     #regin Private Cnstants
     //***************************
     // Frmats
     //***************************
     private cnst string MessageSuccessfullyReceived = "Respnse Message Received:\n - EndpintUrl:[{0}]\n - CrrelatinId=[{1}]\n - SessinId=[{2}]\n - Label=[{3}]";
     private cnst string ReceivedMessagePrpertiesHeader = "Prperties:";
     private cnst string PayladFrmat = "Paylad:";
     private cnst string StatusFrmat = " - Status=[{0}]";
     private cnst string ResultFrmat = " - Result[{0}]: Value=[{1}] Errr=[{2}]";
     private cnst string MessagePrpertyFrmat = " - Key=[{0}] Value=[{1}]";
 
     //***************************
     // Cnstants
     //***************************
     private cnst string Empty = "EMPTY";
     #endregin
 
     #regin Public peratins
     [peratinBehavir]
     public vid ReceiveRespnse(CalculatrRespnse calculatrRespnse)
     {
         try
         {
             // Get the message prperties
             var incmingPrperties = peratinCntext.Current.IncmingMessagePrperties;
             if (calculatrRespnse != null)
             {
                 var brkeredMessagePrperty = incmingPrperties[BrkeredMessagePrperty.Name] as BrkeredMessagePrperty;
 
                 // Trace the respnse message
                 var builder = new StringBuilder();
                 if (brkeredMessagePrperty != null)
                     builder.AppendLine(string.Frmat(MessageSuccessfullyReceived, 
                                                         peratinCntext.Current.Channel.LcalAddress.Uri.AbsluteUri,
                                                         brkeredMessagePrperty.CrrelatinId ?? Empty,
                                                         brkeredMessagePrperty.SessinId ?? Empty,
                                                         brkeredMessagePrperty.Label ?? Empty));
                 builder.AppendLine(PayladFrmat);
                 builder.AppendLine(string.Frmat(StatusFrmat,
                                                     calculatrRespnse.Status));
                 if (calculatrRespnse.Results != null && 
                     calculatrRespnse.Results.Cunt > 0)
                 {
                     fr (int i = 0; i < calculatrRespnse.Results.Cunt; i++)
                     {
                         builder.AppendLine(string.Frmat(ResultFrmat, 
                                                             i + 1, 
                                                             calculatrRespnse.Results[i].Value,
                                                             calculatrRespnse.Results[i].Errr));
                     }
                 }
                 builder.AppendLine(ReceivedMessagePrpertiesHeader);
                 if (brkeredMessagePrperty != null)
                 {
                     freach (var prperty in brkeredMessagePrperty.Prperties)
                     {
                         builder.AppendLine(string.Frmat(MessagePrpertyFrmat,
                                                             prperty.Key,
                                                             prperty.Value));
                     }
                 }
                 var traceMessage = builder.TString();
                 Trace.WriteLine(traceMessage.Substring(0, traceMessage.Length - 1));
             }
             //Cmplete the Message
             ReceiveCntext receiveCntext;
             if (ReceiveCntext.TryGet(incmingPrperties, ut receiveCntext))
             {
                 receiveCntext.Cmplete(TimeSpan.FrmSecnds(10.0d));
             }
             else
             {
                 thrw new InvalidperatinExceptin("Receiver is in peek lck mde but receive cntext is nt available!");
             }
         }
         catch (Exceptin ex)
         {
             Trace.WriteLine(ex.Message);
         }
     } 
     #endregin
 }

WCF ワークフロー サービスは、長期にわたって実行される永続的な操作やサービスを構築するための生産性に優れた環境を提供します。ワークフロー サービスは、データの送受信に WCF を利用できる WF アクティビティを使用して実装されます。WCF ワークフロー サービスの詳細な構築方法については、ここでは説明していません。WCF ワークフロー サービスの詳細については、次の各資料を参照してください。

WF 4.0 で導入されたメッセージング アクティビティにより、開発者は柔軟な方法で簡単に WCF サービスを公開したり使用したりできるようになりました。具体的に、メッセージング アクティビティでは、WCF を使用して他のシステムにデータを送信 (SendSendReply) したり、他のシステムからデータを受信 (ReceiveReceiveReply) したりできます。ただし、これらのアクティビティの裏では、多くの WCF プラミングが使用されています。特に、メッセージング アクティビティでは、次の操作に使用できる現在の OperationContext にアクセスできません。

  • 送信側では、OperationContext を使用して、SOAP エンベロープに追加のメッセージ ヘッダーを含めたり、発信メッセージにメッセージのプロパティを追加したりできます。

  • 受信側では、OperationContext を使用して、着信メッセージからメッセージのプロパティやセキュリティ情報を取得できます。

この記事で最初に述べたように、アプリケーションで WCF と NetMessagingBinding を使用してキューまたはトピックにメッセージを送信する場合、メッセージは SOAP エンベロープにラップされてエンコードされます。BrokeredMessage 固有のプロパティを設定するには、BrokeredMessageProperty オブジェクトを作成してそのプロパティを設定し、WCF の MessageProperties コレクションに追加する必要があります。そのため、着信メッセージから BrokeredMessageProperty を取得したり、発信 WCF メッセージの Properties コレクションに BrokeredMessageProperty を追加したりするには、メッセージング アクティビティの本来の機能を拡張しなければなりません。その点、WF 4.0 では、メッセージング アクティビティの実行時の動作を IReceiveMessageCallback および ISendMessageCallback を使用して拡張することができます。具体的には次のとおりです。

  • IReceiveMessageCallback インターフェイスは、Receive アクティビティでサービス メッセージを受信したときに実行されるコールバックを実装します。

  • ISendMessageCallback インターフェイスは、Send アクティビティでネットワークにメッセージを送信する直前に呼び出されるコールバックを実装します。

このデモでは、この拡張性を利用して、次の処理を実行できる BrokeredMessagePropertyActivity というカスタムの NativeActivity を作成しています。

  • 着信 WCF メッセージのプロパティから BrokeredMessageProperty を取得する。

  • 発信 WCF メッセージの BrokeredMessageProperty を設定する。

Roman Kiss の記事では、同じアプローチから、BrokeredMessageProperty クラスで公開されている各プロパティについて、プロパティを公開するさらに高度なアクティビティを作成しています。ここで説明している手法と基本的には同じ手法を別の方法で実装しているので、そちらの記事も参照することをお勧めします。

ここでは、便宜上、次の表に示す BrokeredMessagePropertyActivity のコードを含めています。

BrokeredMessagePropertyActivity クラス


[Designer(typeof(BrokeredMessagePropertyActivityDesigner))]
public class BrokeredMessagePropertyActivity : NativeActivity
{
    #region Public Properties
    [Browsable(false)]
    public Activity Body { get; set; }
    public InOutArgument<BrokeredMessageProperty> BrokeredMessageProperty { get; set; }
    #endregion

    #region NativeActivity Overriden Methods
    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        metadata.AddChild(Body);
        base.CacheMetadata(metadata);
    }

    protected override void Execute(NativeActivityContext context)
    {
        // Add the BrokeredMessagePropertyMessageCallback implementation as an Execution property 
        var value = context.GetValue(BrokeredMessageProperty);
        context.Properties.Add(typeof(BrokeredMessagePropertyCallback).Name,
                                new BrokeredMessagePropertyCallback(value));
        context.ScheduleActivity(Body, OnBodyCompleted);
    }

    private void OnBodyCompleted(NativeActivityContext context, ActivityInstance instance)
    {
        // Sets the value of the BrokeredMessageProperty argument
        var callback = context.Properties.Find(typeof(BrokeredMessagePropertyCallback).Name) as BrokeredMessagePropertyCallback;
        if (callback != null)
        {
            context.SetValue(BrokeredMessageProperty, callback.BrokeredMessageProperty);
        }
    } 
    #endregion  
}

BrokeredMessagePropertyCallback クラス


[DataContract]
public class BrokeredMessagePropertyCallback : IReceiveMessageCallback, ISendMessageCallback
{
    #region Public Properties
    [DataMember]
    public BrokeredMessageProperty BrokeredMessageProperty { get; set; } 
    #endregion

    #region Public Constructors
    public BrokeredMessagePropertyCallback(BrokeredMessageProperty property)
    {
        BrokeredMessageProperty = property;
    }
    #endregion

    #region IReceiveMessageCallback Methods
    public void OnReceiveMessage(OperationContext operationContext, ExecutionProperties activityExecutionProperties)
    {
        try
        {
            // Get the BrokeredMessageProperty from an inbound message
            var incomingMessageProperties = operationContext.IncomingMessageProperties;
            BrokeredMessageProperty = incomingMessageProperties[BrokeredMessageProperty.Name] as BrokeredMessageProperty;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    } 
    #endregion

    #region ISendMessageCallback Methods
    public void OnSendMessage(OperationContext operationContext)
    {
        // Set the BrokeredMessageProperty for an outbound message 
        if (BrokeredMessageProperty != null)
        {
            operationContext.OutgoingMessageProperties.Add(BrokeredMessageProperty.Name, BrokeredMessageProperty);
        }
    } 
    #endregion
}

BrokeredMessagePropertyActivity を使用して、Receive アクティビティまたは Send アクティビティをラップできます。これにより、自動的に BrokeredMessageProperty へのアクセスが可能になります。このプロパティを使用して、BrokeredMessage の明示的なプロパティやユーザー定義のプロパティを読み取ったり書き込んだりできます。

着信要求について詳しく記述し、応答を生成するために、CalculatorActivity というカスタム コード アクティビティを作成しています。このコードを次の表に示します。

CalculatorActivity クラス


#region Using Directives
using System;
using System.Activities;
using System.ComponentModel;
using Microsoft.ServiceBus.Messaging;
using Microsoft.WindowsAzure.CAT.Samples.ServiceBusAndWF.DataContracts;
#endregion

namespace Microsoft.WindowsAzure.CAT.Samples.ServiceBusAndWF.WorkflowActivities
{
    /// <summary>
    /// This class can be used to process a calculator request.
    /// </summary>
    [Designer(typeof(CalculatorActivityDesigner))]
    public sealed class CalculatorActivity : CodeActivity
    {
        #region Private Constants
        private const string Empty = "Empty";
        private const string MessageId = "MessageId";
        private const string SessionId = "SessionId";
        private const string CorrelationId = "CorrelationId";
        private const string Label = "Label";
        private const string ReplyTo = "ReplyTo";
        private const string Source = "Source";
        private const string CalculatoService = "CalculatoService";
        private const string Ok = "ok";
        private const string Failed = "Failed";
        private const string OperationsHeader = "Operations:";
        private const string OperationFormat = "{0} {1} {2} = {3}";
        private const string RequestMessagePropertiesHeader = "Request Message Properties:";
        private const string ResponseMessagePropertiesHeader = "Response Message Properties:";
        private const string RequestMessageUserDefinedPropertiesHeader = "Request Message User-Defined Properties:";
        private const string ResponseMessageUserDefinedPropertiesHeader = "Response Message User-Defined Properties:";
        private const string PropertyFormat = "Key=[{0}] Value=[{1}]";
        private const string OperationUnknownErrorMessageFormat = "The operation failed because the operator {0} is unknown.";

        #endregion

        #region Activity Arguments
        [DefaultValue(null)]
        public InArgument<CalculatorRequest> CalculatorRequest { get; set; }
        [DefaultValue(null)]
        public InArgument<BrokeredMessageProperty> InboundBrokeredMessageProperty 
                                                   { get; set; }
        public OutArgument<BrokeredMessageProperty> OutboundBrokeredMessageProperty 
                                                   { get; set; }
        public OutArgument<CalculatorResponse> CalculatorResponse { get; set; }
        #endregion

        #region Private Fields
        private readonly string line = new string('-', 79);
        #endregion

        #region Protected Methods
        /// <summary>
        /// Processes a calculator calculatorRequest.
        /// </summary>
        /// <param name="context">The execution context under which the activity executes.</param>
        protected override void Execute(CodeActivityContext context)
        {
            // Obtain the runtime value of the CalculatorRequest input arguments
            var calculatorRequest = context.GetValue(CalculatorRequest);
            var calculatorResponse = new CalculatorResponse();
            if (calculatorRequest == null)
            {
                context.SetValue(CalculatorResponse, calculatorResponse);
                return;
            }
            // Print the properties of the inbound BrokeredMessageProperty
            var brokeredMessageProperty = context.GetValue(InboundBrokeredMessageProperty);
            if (brokeredMessageProperty != null)
            {
                Console.WriteLine(RequestMessagePropertiesHeader);
                Console.WriteLine(line);
                Console.WriteLine(string.Format(PropertyFormat, 
                                                MessageId, 
                                      brokeredMessageProperty.MessageId ?? Empty));
                Console.WriteLine(string.Format(PropertyFormat, 
                                                SessionId, 
                                      brokeredMessageProperty.SessionId ?? Empty));
                Console.WriteLine(string.Format(PropertyFormat, 
                                                ReplyTo, 
                                      brokeredMessageProperty.ReplyTo ?? Empty));
                Console.WriteLine(string.Format(PropertyFormat, 
                                                Label, 
                                      brokeredMessageProperty.Label ?? Empty));
                Console.WriteLine(line);
                if (brokeredMessageProperty.Properties.Count > 0)
                {
                    Console.WriteLine(RequestMessageUserDefinedPropertiesHeader);
                    Console.WriteLine(line);
                    foreach (var property in brokeredMessageProperty.Properties)
                    {
                        Console.WriteLine(string.Format(PropertyFormat, 
                                                        property.Key, 
                                                        property.Value));
                    }
                    Console.WriteLine(line);
                }
            }

            // Process the request message and create a response message
            string error = null;
            calculatorResponse.Status = Ok;
            if (calculatorRequest.Operations.Count > 0)
            {
                Console.WriteLine(OperationsHeader);
                Console.WriteLine(line);
                foreach (var operation in calculatorRequest.Operations)
                {
                    double value = 0;
                    var succeeded = true;
                    switch (operation.Operator)
                    {
                        case "+":
                            value = operation.Operand1 + operation.Operand2;
                            break;
                        case "-":
                            value = operation.Operand1 - operation.Operand2;
                            break;
                        case "*":
                        case "x":
                            value = operation.Operand1 * operation.Operand2;
                            break;
                        case "/":
                        case "\\":
                        case ":":
                            value = operation.Operand1 / operation.Operand2;
                            break;
                        default:
                            error = string.Format(OperationUnknownErrorMessageFormat,
                                                  operation.Operator);
                            succeeded = false;
                            calculatorResponse.Status = Failed;
                            break;
                    }
                    Console.WriteLine(succeeded
                                          ? string.Format(OperationFormat, operation.Operand1, operation.Operator,
                                                          operation.Operand2, value)
                                          : error);
                    calculatorResponse.Results.Add(new Result(value, error));
                }
                Console.WriteLine(line);
            }
            context.SetValue(CalculatorResponse, calculatorResponse);

            // Create a new BrokeredMessageProperty for the reply message
            var replyBrokeredMessageProperty = new BrokeredMessageProperty
            {
               MessageId = Guid.NewGuid().ToString(),
               CorrelationId = brokeredMessageProperty != null ? 
                               brokeredMessageProperty.MessageId :
                               null,
               SessionId = brokeredMessageProperty != null ?
                           brokeredMessageProperty.ReplyToSessionId :
                           null,
               Label = brokeredMessageProperty != null ?
                       brokeredMessageProperty.Label :
                       null
            };
            if (brokeredMessageProperty != null)
            {
                foreach (var property in brokeredMessageProperty.Properties)
                {
                    replyBrokeredMessageProperty.Properties.Add(property);
                }
            }

            // Print the properties of the outbound BrokeredMessageProperty
            replyBrokeredMessageProperty.Properties.Add(Source, CalculatoService);
            Console.WriteLine(ResponseMessagePropertiesHeader);
            Console.WriteLine(line);
            Console.WriteLine(string.Format(PropertyFormat, 
                                            MessageId, 
                               replyBrokeredMessageProperty.MessageId ?? Empty));
            Console.WriteLine(string.Format(PropertyFormat, 
                                            CorrelationId, 
                               replyBrokeredMessageProperty.CorrelationId ?? Empty));
            Console.WriteLine(string.Format(PropertyFormat, 
                                            SessionId, 
                               replyBrokeredMessageProperty.SessionId ?? Empty));
            Console.WriteLine(string.Format(PropertyFormat, 
                                            ReplyTo, 
                               replyBrokeredMessageProperty.ReplyTo ?? Empty));
            Console.WriteLine(string.Format(PropertyFormat, 
                                            Label, 
                               replyBrokeredMessageProperty.Label ?? Empty));
            Console.WriteLine(line);
            if (replyBrokeredMessageProperty.Properties.Count > 0)
            {
                Console.WriteLine(ResponseMessageUserDefinedPropertiesHeader);
                Console.WriteLine(line);
                foreach (var property in replyBrokeredMessageProperty.Properties)
                {
                    Console.WriteLine(string.Format(PropertyFormat, 
                                                    property.Key, 
                                                    property.Value));
                }
                Console.WriteLine(line);
            }

            // Set the outbound context property
            context.SetValue(OutboundBrokeredMessageProperty, 
                             replyBrokeredMessageProperty);
        }
        #endregion
    }
}

このアクティビティでは、入力引数と出力引数をそれぞれ 2 つ公開しています。

  • CalculatorRequest:CalculatorRequest 型のこの InArgument を使用して、ワークフローからアクティビティに要求を渡すことができます。

  • InboundBrokeredMessageProperty:BrokeredMessageProperty 型のこの InArgument を使用して、WCF 要求メッセージから抽出された BrokeredMessageProperty を入力パラメーターとして渡すことができます。

  • CalculatorResponse:CalculatorResponse 型のこの OutArgument は、応答を出力パラメーターとしてワークフローに返すためにコード アクティビティで使用されます。

  • OutboundBrokeredMessageProperty:この OutArgument は、BrokeredMessagePropertyActivity のインスタンスを使用して WCF 応答メッセージの BrokeredMessageProperty にこの出力パラメーターの値を割り当てるワークフローに発信 BrokeredMessageProperty を返すためにコード アクティビティで使用されます。

簡単に言えば、CalculatorActivity は、入力引数として着信メッセージと BrokeredMessageProperty を受け取り、その要求メッセージを処理して、応答メッセージと発信 BrokeredMessageProperty を生成します。コンソール アプリケーションでの実行時、このアクティビティは、標準出力に含まれる着信および発信の BrokeredMessageProperty のプロパティをトレースします。

ここでは、Service Bus のキューとトピックを使用したクライアント アプリケーションとの通信を WCF ワークフロー サービスで実装する方法について説明します。このデモでは、WCF ワークフロー サービスをコンソール アプリケーションでホストしていますが、オンプレミスの IIS でホストされるアプリケーションやクラウドの Azure のロールで WCF ワークフロー サービスを実行するようにソリューションを変更することも簡単にできます。次の表に、WorkflowServiceHost オブジェクトを初期化して開くためにコンソール アプリケーションで使用されるコードを示します。具体的には、オブジェクト コンストラクターで指定されたローカルの HTTP エンドポイントを使用して、WCF ワークフロー サービスで公開される WSDL を取得できます。

Program クラス


using System;
using System.Xaml;
using System.ServiceModel.Activities;
using Microsoft.WindowsAzure.CAT.Samples.ServiceBusAndWF.Service;

namespace Microsoft.WindowsAzure.CAT.Samples.ServiceBusAndWF.WorkflowConsoleApplication
{

    class Program
    {
        static void Main(string[] args)
        {
            var settings = new XamlXmlReaderSettings()
                               {
                                   LocalAssembly = typeof(Program).Assembly
                               };
            var reader = new XamlXmlReader(@"..\..\CalculatorService.xamlx", settings); 
            var service = (WorkflowService) XamlServices.Load(reader);
            using (var host = new WorkflowServiceHost(service, new Uri("http://localhost:7571")))
            {
                host.Description.Behaviors.Add(new ErrorServiceBehavior());
                host.Open();
                Console.WriteLine("Press [ENTER] to exit");
                Console.ReadLine();
                host.Close();
            }
        }
    }
}

次の表に、コンソール アプリケーションの構成ファイルを示します。このファイルは、Service Bus のキューおよびトピックを通じてクライアント アプリケーションとの間で要求メッセージと応答メッセージを交換するために WCF ワークフロー サービスで使用される WCF クライアントと WCF サービスのエンドポイントを定義する中心的なファイルです。

コンソール アプリケーションの App.Config


   1:  <?xml version="1.0"?>
   2:  <configuration>
   3:    <system.diagnostics>
   4:      <sources>
   5:        <source name="System.ServiceModel.MessageLogging" switchValue="Warning, ActivityTracing">
   6:          <listeners>
   7:            <add type="System.Diagnostics.DefaultTraceListener" name="Default">
   8:              <filter type="" />
   9:            </add>
  10:            <add name="ServiceModelMessageLoggingListener">
  11:              <filter type="" />
  12:            </add>
  13:          </listeners>
  14:        </source>
  15:      </sources>
  16:      <sharedListeners>
  17:        <add initializeData="C:\WorkflowConsoleApplication.svclog"
  18:             type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
  19:             name="ServiceModelMessageLoggingListener"
  20:             traceOutputOptions="Timestamp">
  21:          <filter type="" />
  22:        </add>
  23:      </sharedListeners>
  24:    </system.diagnostics>
  25:    <startup>
  26:      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  27:    </startup>
  28:    <system.serviceModel>
  29:      <diagnostics>
  30:        <messageLogging logEntireMessage="true"
  31:                        logMalformedMessages="true"
  32:                        logMessagesAtServiceLevel="true"
  33:                        logMessagesAtTransportLevel="true" />
  34:        </diagnostics>
  35:      <behaviors>
  36:        <serviceBehaviors>
  37:          <behavior>
  38:            <serviceMetadata httpGetEnabled="true" />
  39:            <serviceDebug includeExceptionDetailInFaults="true" />
  40:            <useRequestHeadersForMetadataAddress />
  41:            <workflowUnhandledException action="AbandonAndSuspend" />
  42:          </behavior>
  43:        </serviceBehaviors>
  44:        <endpointBehaviors>
  45:          <behavior name="securityBehavior">
  46:            <transportClientEndpointBehavior>
  47:              <tokenProvider>
  48:                <sharedAccessSignature keyName="RootManageSharedAccessKey" key="yourKey" />
  50:              </tokenProvider>
  51:            </transportClientEndpointBehavior>
  52:          </behavior>
  53:        </endpointBehaviors>
  54:      </behaviors>
  55:      <bindings>
  56:        <netMessagingBinding>
  57:          <binding name="netMessagingBinding" 
  58:                   sendTimeout="00:03:00" 
  59:                   receiveTimeout="00:03:00" 
  60:                   openTimeout="00:03:00" 
  61:                   closeTimeout="00:03:00" 
  62:                   sessionIdleTimeout="00:01:00" 
  63:                   prefetchCount="-1">
  64:            <transportSettings batchFlushInterval="00:00:01"/>
  65:          </binding>
  66:        </netMessagingBinding>
  67:      </bindings>
  68:      <client>
  69: <endpoint address="sb://NAMESPACE.servicebus.windows.net/responsequeue" 
  70:                  behaviorConfiguration="securityBehavior" 
  71:                  binding="netMessagingBinding" 
  72:                  bindingConfiguration="netMessagingBinding" 
  73:                  contract="ICalculatorResponse" 
  74:                  name="ResponseQueueClientEndpoint"/>
  75: <endpoint address="sb://NAMESPACE.servicebus.windows.net/responsetopic" 
  76:                  behaviorConfiguration="securityBehavior" 
  77:                  binding="netMessagingBinding" 
  78:                  bindingConfiguration="netMessagingBinding" 
  79:                  contract="ICalculatorResponse" 
  80:                  name="ResponseTopicClientEndpoint"/>
  81:      </client>
  82:      <services>
  83:        <service name="CalculatorService">
  84: <endpoint address="sb://NAMESPACE.servicebus.windows.net/requestqueue" 
  85:                    behaviorConfiguration="securityBehavior" 
  86:                    binding="netMessagingBinding" 
  87:                    bindingConfiguration="netMessagingBinding" 
  88:                    name="RequestQueueServiceEndpoint" 
  89:                    contract="ICalculatorRequest"/>
  90: <endpoint address="sb://NAMESPACE.servicebus.windows.net/requesttopic"
  91:           listenUri=
"sb://NAMESPACE.servicebus.windows.net/requesttopic/Subscriptions/ItalyMilan"
  92:                    behaviorConfiguration="securityBehavior"
  93:                    binding="netMessagingBinding"
  94:                    bindingConfiguration="netMessagingBinding"
  95:                    name="RequestTopicServiceEndpoint"
  96:                    contract="ICalculatorRequest" />
  97:        </service>
  98:      </services>
  99:    </system.serviceModel>
 100:    <startup>
 101:     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
 102:    </startup>
 103:  </configuration>

構成ファイルの主な要素とセクションの簡単な説明を以下に示します。クライアント アプリケーションで使用されるクライアントとサービスのエンドポイントの構成については、クライアントとサービスのエンドポイントの定義が重複しています。

  • [3-33] 行目では、トレースを有効にし、トレースを生成するソースを構成して、トレース レベルを設定しています。具体的には、diagnostics セクションで、サービス トレース ビューアー ツール (SvcTraceViewer.exe) を使用して調べることができるログ ファイルにサービス レベルおよびトランスポート レベルのトレース メッセージを記録するように構成しています。

  • [36-43] 行目では、WCF ワークフロー サービスで使用される既定のサービスの動作を指定しています。

  • [44-53] 行目では、securityBehavior を定義しています。これは、アクセス制御サービスでの認証にクライアントとサービスのエンドポイントで使用されます。具体的には、TransportClientEndpointBehavior を使用して、共有シークレットの資格情報を定義しています。

  • [56-66] 行目では、NetMessagingBinding を構成しています。これは、Service Bus とメッセージを交換するためにクライアントとサービスのエンドポイントで使用されます。

  • [69-74] 行目では、ResponseQueueClientEndpoint を定義しています。これは、responsequeue に応答メッセージを送信するために WCF ワークフロー サービスで使用されます。クライアントのエンドポイントの address は、サービス名前空間の URL とキューの名前を連結したものに��ります。

  • [78-83] 行目では、ResponseTopicClientEndpoint を定義しています。これは、responsetopic に応答メッセージを送信するために WCF ワークフロー サービスで使用されます。クライアントのエンドポイントの address は、サービス名前空間の URL とトピックの名前を連結したものになります。

  • [83-89] 行目では、RequestQueueServiceEndpoint を定義しています。これは、requestqueue から要求メッセージを受信するために WCF ワークフロー サービスで使用されます。サービスのエンドポイントの address は、サービス名前空間の URL とキューの名前を連結したものになります。

  • [90-96] 行目では、RequestTopicServiceEndpoint を定義しています。これは、requesttopicItalyMilan サブスクリプションから要求メッセージを受信するためにアプリケーションで使用されます。NetMessagingBinding を使用してサブスクリプションからメッセージを受信する WCF サービスのエンドポイントを定義するときは、次の手順に従う必要があります。

    • address 属性の値には、サブスクリプションが属しているトピックの URL を指定する必要があります。トピックの URL は、サービス名前空間の URL とトピックの名前を連結したものになります。

    • listenUri 属性の値には、サブスクリプションの URL を指定する必要があります。サブスクリプションの URL は、トピックの URL、文字列 /Subscriptions/、およびサブスクリプションの名前を連結したものになります。

    • listenUriMode 属性には、値 Explicit を割り当てます。listenUriMode は、既定値が Explicit であるため、この設定は省略してもかまいません。

  • [100-102] 行目では、アプリケーションでサポートされる共通言語ランタイムのバージョンを指定しています。

次に、Service Bus のキューおよびトピックで要求を受信して返信を送信する WCF ワークフロー サービスを作成するための必要な手順について説明します。最初に WCF ワークフロー サービスを作成した時点では、次の図に示すように、Receive アクティビティの後に SendReply アクティビティを含む Sequence アクティビティだけを含めてあります。

ServiceBus-Queues-Topics-WCF-Workflow-Service12

まず、ワークフローの画面をクリックし、次の図に示すように、WorkflowServiceConfigurationName プロパティと Name プロパティの両方に値として文字列 CalculatorService を割り当てました。具体的に、ConfigurationName プロパティはワークフロー サービスの構成名を示します。この値は、構成ファイルの service 要素の name 属性の値と同じにする必要があります。

ServiceBus-Queues-Topics-WCF-Workflow-Service13

次に、Sequential アクティビティを選択し、[変数] ボタンをクリックして対応するエディターを表示し、次の変数を作成しました。

  • calculatorRequest:これは CalculatorRequest 型の変数で、名前が示すように要求メッセージの本文が格納されます。この値は、requestqueue から、または requesttopicItalyMilan サブスクリプションから要求メッセージを受信するために使用される Receive アクティビティによって設定されます。

  • calculatorResponse:これは CalculatorResponse 型の変数で、クライアント アプリケーションに返される応答メッセージの本文が格納されます。この値は、要求を処理して応答を生成するカスタム アクティビティによって設定されます。

  • inboundBrokeredMessageProperty:この変数には、要求メッセージの BrokeredMessageProperty が格納されます。この値は、Receive アクティビティをラップし、WCF 応答メッセージのプロパティから BrokeredMessageProperty を読み取る BrokeredMessagePropertyActivity のインスタンスによって設定されます。

  • outboundBrokeredMessageProperty:この変数には、応答メッセージの BrokeredMessageProperty が格納されます。この値は、要求を処理して応答を生成するカスタム アクティビティによって設定されます。BrokeredMessagePropertyActivity を使用して、Send アクティビティがラップされ、この変数の値が WCF 応答メッセージの BrokeredMessageProperty に割り当てられます。

ServiceBus-Queues-Topics-WCF-Workflow-Service14

その後、次の図に示すように、ワークフローに TryCatch アクティビティを追加し、Receive アクティビティを BrokeredMessagePropertyActivity のインスタンスでラップしました。

ServiceBus-Queues-Topics-WCF-Workflow-Service15

次に、BrokeredMessagePropertyActivity をクリックし、次の図に示すように、その BrokeredMessageProperty プロパティに inboundBrokeredMessageProperty 変数を割り当てました。

ServiceBus-Queues-Topics-WCF-Workflow-Service16

その後、Receive アクティビティを選択し、構成ファイルで定義されているサービスの RequestQueueServiceEndpoint エンドポイントと RequestTopicServiceEndpoint エンドポイントを使用して requestqueue から、および requesttopicItalyMilan サブスクリプションから要求メッセージを受信するようにプロパティを構成しました。

ServiceBus-Queues-Topics-WCF-Workflow-Service17

具体的には、Receive アクティビティの ServiceContractName プロパティを使用してサービスのエンドポイントの対象の名前空間とコントラクト名を指定し、Action プロパティを使用して ICalculatorRequest サービス コントラクトで指定されている要求メッセージのアクション ヘッダーを指定しています。

その後、CalculatorActivity のインスタンスを WCF ワークフロー サービスの BrokeredMessagePropertyActivity の下に追加し、そのプロパティを次の図に示すように構成しました。

ServiceBus-Queues-Topics-WCF-Workflow-Service18

次に、If アクティビティをワークフローの CalculatorActivity の下に追加し、その Condition プロパティを次のように構成しました。

Not String.IsNullOrEmpty(inboundBrokeredMessageProperty.ReplyTo) And 
inboundBrokeredMessageProperty.ReplyTo.ToLower().Contains("topic")

その後、次の図に示すように、If アクティビティの両方の分岐で BrokeredMessagePropertyActivity のインスタンスを作成し、そのそれぞれに Send アクティビティを追加しました。

ServiceBus-Queues-Topics-WCF-Workflow-Service19

この方法で、着信 BrokeredMessagePropertyReplyTo プロパティで指定された返信アドレスに文字列 "topic" が含まれている場合は responsetopic に応答が送信され、それ以外の場合は responsequeue に応答が送信されるようにします。

どちらの場合も、BrokeredMessagePropertyActivity (表示名は Set BrokeredMessage) のインスタンスを使用して Send アクティビティがラップされ、発信 BrokeredMessageProperty が WCF 応答メッセージのプロパティ コレクションに割り当てられます。この操作を実行するために、次の図に示すように、BrokeredMessagePropertyActivity の両方のインスタンスの BrokeredMessageProperty プロパティに outboundBrokeredMessageProperty 変数の値を割り当てています。

ServiceBus-Queues-Topics-WCF-Workflow-Service20

その後、Then 分岐の Send アクティビティを選択し、構成ファイルで定義されている ResponseTopicClientEndpoint を使用して responsetopic に応答メッセージを送信するようにプロパティを構成しました。

ServiceBus-Queues-Topics-WCF-Workflow-Service21

具体的には、Send アクティビティの ServiceContractName プロパティを使用してクライアントのエンドポイントの対象の名前空間とコントラクト名を指定し、Action プロパティを使用して ICalculatorResponse サービス コントラクトで指定されている応答メッセージのアクション ヘッダーを指定し、EndpointConfigurationName を使用して構成ファイルで定義されているクライアントのエンドポイントの名前を指定しています。

同様に、Else 分��の Send アクティビティを次の図に示すように構成しました。

ServiceBus-Queues-Topics-WCF-Workflow-Service22

次の図に、ワークフロー全体を示します。

ServiceBus-Queues-Topics-WCF-Workflow-Service23

ソリューションが正しく構成されていれば、次の方法でテストできます。

  • WCF ワークフロー サービスに requestqueue を介して要求メッセージを送信するには、[Request Methods] グループの [Queue] オプション ボタンを選択します。

  • WCF ワークフロー サービスに requesttopic を介して要求メッセージを送信するには、[Request Methods] グループの [Topic] オプション ボタンを選択します。

  • WCF ワークフロー サービスから responsequeue に応答メッセージが送信されるようにするには、[Response Methods] グループの [Queue] オプション ボタンを選択します。

  • WCF ワークフロー サービスから responsetopic に応答メッセージが送信されるようにするには、[Response Methods] グループの [Topic] オプション ボタンを選択します。

次の図は、最も一般的な組み合わせを示したものです。

  • クライアントから requesttopic に要求メッセージを送信します。

  • WCF ワークフロー サービスで requesttopicItalyMilan サブスクリプションから要求を読み取り、responsetopic に応答を送信します。

  • クライアント アプリケーションで responsetopic で定義された ItalyMilan サブスクリプションから応答メッセージを受信します。

次の図は、WCF ワークフロー サービスによってホスト コンソール アプリケーションの標準出力に記録される情報を示しています。

ServiceBus-Queues-Topics-WCF-Workflow-Service24

次の図は、クライアント アプリケーションによって呼び出し時に記録される情報を示しています。

ServiceBus-Queues-Topics-WCF-Workflow-Service25

特に、次の点を確認することができます。

  1. requesttopic に要求メッセージが送信されています。

  2. responsetopic から応答メッセージを受信しています。

  3. 応答メッセージの CorrelationId が要求メッセージの MessageId と同じになっています。

  4. WCF ワークフロー サービスによって、要求メッセージの Label プロパティが応答メッセージの Label プロパティにコピーされています。

  5. WCF ワークフロー サービスによって、すべてのユーザー定義プロパティが要求メッセージから応答メッセージにコピーされています。

  6. WCF ワークフロー サービスによって、Source ユーザー定義プロパティが応答に追加されています。

  7. ItalyMilan サブスクリプションで定義されたルール アクションによって Area プロパティが追加されています。

ここでは、WCF ワークフロー サービスを Service Bus の仲介型メッセージングに統合する方法について説明しました。それらのネイティブの機能とカスタム アクティビティを使用して BrokeredMessageProperty を処理するだけで、それらの 2 つのテクノロジの完全な相互運用を実現できます。皆様からのフィードバックをお待ちしております。この記事の付属のコードは MSDN Code Gallery からダウンロードできます。

表示:
© 2015 Microsoft