[Dynamics CRM 2011]Dynamics CRM 2011と連携する AppFabric サービスバス リスナーアプリケーションを作るその1 からの続きです。AppFabric Service Bus の作成とService Endpoint の登録を行ったので、今回は実際にAppFabric solution listener アプリケーションを作ってみます。
リスナーアプリケーションを作成するにあたり、Dynamics CRM 2011 SDK に加えて次のSDKが必要です。Windows Azure SDKはたぶん不要のはずですが念のため。必要に応じてインストールしてください。検証時のSDKのバージョンはいずれも 1.5 でした。
- Windows Azure SDK
- Windows Azure AppFabric SDK
http://www.windowsazure.com/ja-jp/develop/downloads/ のページのスタンドアロンのWindows Azure SDKのステップ2:SDKのインストールにあるSDKのリンクから Azure SDK をダウンロードできます。同ページのWindows Azure AppFabric SDKのダウンロードリンクから AppFabric SDK をダウンロードできます。Windows Azure Tools for Microsoft Visual Studio 等も一緒にインストールしたい場合は、リンク先の適当なインストールオプションを選択してインストールを行ってください。
1. AppFabric Solution Listener アプリケーションの作成
Azure AppFabric Service Bus にAppFabric Service Bus 連携用のプラグインから非同期サービス経由でポストされたプラグインのメッセージをリッスンするプログラムを作成します。
Visual Studio 2010 を起動します。 ソリューションを新規作成します。テンプレートにコンソールアプリケーションを指定してソリューションを作成します。

ソリューションエクスプローラ上の作成したプロジェクトを右クリックしてコンテキストメニューのプロパティをクリックします。プロジェクトのプロパティ画面が表示されます。アプリケーションタブの対象のフレームワークを.NET Framework 4 を選択します。

次に必要なアセンブリの参照を設定します。参照の設定で、 Dynamics CRM SDK の sdk\bin フォルダにある、microsoft.crm.sdk.proxy.dll, microsoft.xrm.sdk.dllの参照を追加します。

同様に Microsoft.ServiceBus.dll も参照に追加します。標準のインストールでは、C:\Program Files\Windows Azure AppFabric SDK\V1.5\Assemblies\NET4.0 に格納されています。V1.5はSDKのバージョンによって変わると思います。

そのほかGACにある、System.ServiceModel.dll, System.Runtime.Serialization への参照を追加します。
以上でアセンブリ参照は完了です。次のサンプルプログラムを作成します。
既定で作成される Program.cs を次のように編集します。サンプルプログラムは SDKのサンプルとほぼ同じです。サービスバスからプラグインのメッセージをリッスンするサービスクラスは IServiceEndpointPlugin を実装する必要があります。トリガーとなるメッセージがService Endpoint 経由で AppFabric Service Bus にポストされると、 Execute メソッドが実行されます。プログラムで servicebusNamespace に指定しているのはサービスバス作成時に設定した名前空間名です。 issuerName と issuerKey は同じくAzure ポータルのサービスバスの画面で確認できるサービスバスの(画面右側に表示される)プロパティの既定のキーを表示ボタンをクリックして表示される既定の発行者と既定のキーを指定します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk;
using System.ServiceModel;
using Microsoft.ServiceBus;
using System.ServiceModel.Description;
namespace AzureServiceBusIntegrationSample
{
class Program
{
static void Main(string[] args)
{
ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Http;
string servicebusNamespace = "Netplanetes-Crm-Sample";
string issuerName = "XXXX";
string issuerKey = "XXXXXXXXXXXXXXXXX";
// Address の作成
Uri address = ServiceBusEnvironment.CreateServiceUri(Uri.UriSchemeHttps, servicebusNamespace, "SampleService");
TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
sharedSecretServiceBusCredential.TokenProvider = TokenProvider.CreateSharedSecretTokenProvider(issuerName, issuerKey);
//sharedSecretServiceBusCredential .Credentials.SharedSecret.IssuerName = issuerName;
//sharedSecretServiceBusCredential .Credentials.SharedSecret.IssuerSecret = issuerKey;
// Bindingの作成
WS2007HttpRelayBinding binding = new WS2007HttpRelayBinding();
binding.Security.Mode = EndToEndSecurityMode.Transport;
// ServiceHostの作成
ServiceHost serviceHost = new ServiceHost(typeof(SampleBehavior));
// エンドポイントの追加
serviceHost.AddServiceEndpoint(typeof(IServiceEndpointPlugin), binding, address);
// エンドポイント用に ServiceRegistrySettings Behaviorを作成する
IEndpointBehavior serviceRegistrySettings = new ServiceRegistrySettings(DiscoveryType.Public);
// サービスバスの資格情報をすべてのエンドポイントに追加する
foreach (ServiceEndpoint endpoint in serviceHost.Description.Endpoints)
{
endpoint.Behaviors.Add(serviceRegistrySettings);
endpoint.Behaviors.Add(sharedSecretServiceBusCredential);
}
// サービスホストを開く
serviceHost.Open();
// サービスを閉じる
Console.WriteLine("終了するには Enter を入力してください。");
Console.ReadLine();
// Close the service.
serviceHost.Close();
Console.WriteLine("プログラムを終了します.");
}
}
/// <summary>
/// Windows Azure の AppFabric 経由でイベントを
/// リッスンするプラグインはIServiceEndpointPluginを実装する
/// </summary>
[ServiceBehavior]
class SampleBehavior : IServiceEndpointPlugin
{
public void Execute(RemoteExecutionContext executionContext)
{
if (executionContext == null)
{
Console.WriteLine("コンテキストがnullです");
return;
}
Console.WriteLine(executionContext.MessageName);
Guid id = (Guid)executionContext.OutputParameters["id"];
Console.WriteLine(id.ToString());
printContextInfo(executionContext);
}
/// <summary>
/// context情報を出力する
/// SDK のサンプルから拝借
/// </summary>
/// <param name="context"></param>
private void printContextInfo(RemoteExecutionContext context)
{
Console.WriteLine("----------");
Console.WriteLine("UserId: {0}", context.UserId);
Console.WriteLine("OrganizationId: {0}", context.OrganizationId);
Console.WriteLine("OrganizationName: {0}", context.OrganizationName);
Console.WriteLine("MessageName: {0}", context.MessageName);
Console.WriteLine("Stage: {0}", context.Stage);
Console.WriteLine("Mode: {0}", context.Mode);
Console.WriteLine("PrimaryEntityName: {0}", context.PrimaryEntityName);
Console.WriteLine("SecondaryEntityName: {0}", context.SecondaryEntityName);
Console.WriteLine("BusinessUnitId: {0}", context.BusinessUnitId);
Console.WriteLine("CorrelationId: {0}", context.CorrelationId);
Console.WriteLine("Depth: {0}", context.Depth);
Console.WriteLine("InitiatingUserId: {0}", context.InitiatingUserId);
Console.WriteLine("IsExecutingOffline: {0}", context.IsExecutingOffline);
Console.WriteLine("IsInTransaction: {0}", context.IsInTransaction);
Console.WriteLine("IsolationMode: {0}", context.IsolationMode);
Console.WriteLine("Mode: {0}", context.Mode);
Console.WriteLine("OperationCreatedOn: {0}", context.OperationCreatedOn.ToString());
Console.WriteLine("OperationId: {0}", context.OperationId);
Console.WriteLine("PrimaryEntityId: {0}", context.PrimaryEntityId);
Console.WriteLine("OwningExtension LogicalName: {0}", context.OwningExtension.LogicalName);
Console.WriteLine("OwningExtension Name: {0}", context.OwningExtension.Name);
Console.WriteLine("OwningExtension Id: {0}", context.OwningExtension.Id);
//Console.WriteLine("SharedVariables: {0}", (context.SharedVariables == null ? "NULL" :
// SerializeParameterCollection(context.SharedVariables)));
//Console.WriteLine("InputParameters: {0}", (context.InputParameters == null ? "NULL" :
// SerializeParameterCollection(context.InputParameters)));
//Console.WriteLine("OutputParameters: {0}", (context.OutputParameters == null ? "NULL" :
// SerializeParameterCollection(context.OutputParameters)));
//Console.WriteLine("PreEntityImages: {0}", (context.PreEntityImages == null ? "NULL" :
// SerializeEntityImageCollection(context.PreEntityImages)));
//Console.WriteLine("PostEntityImages: {0}", (context.PostEntityImages == null ? "NULL" :
// SerializeEntityImageCollection(context.PostEntityImages)));
Console.WriteLine("----------");
}
}
}
サンプルプログラムはEnterキーを入力すると停止するように作成されています。作成したアプリケーションをビルドしデバッグ実行します。次の動作確認をしている間はプログラムを終了しないようにします。
コントラクトがTwo-WayやREST形式の場合のバインディングとコントラクトの組み合わせは次のURLを参照してください。
Windows Azure ソリューション用のリスナーの記述
http://msdn.microsoft.com/ja-jp/library/gg309615.aspx
以上で準備は完了です。
2.動作の確認
リスナーの動作を確認するために、取引先企業を実際に作成してみます。

取引先企業を作成ご、システムジョブを確認すると、サービスエンドポイントのステップの実行用のシステムジョブが作成され、動作していることを確認できます。処理が成功すると、ステータスが成功しましたになります。しばらくするとデバッグ実行しているコンソールにログ情報が出力されます。

サービスエンドポイントとステップを構成しているにも関わらず、リスナーアプリケーションが存在しなかった場合どうなるでしょうか?コントラクトを打ちっぱなしのOne-Way としていますがリスナーアプリケーションが動作していないとシステムジョブのステータスは大気中になります。

システムジョブの画面を表示す路t、エラーが発生したことを表すメッセージが表示されます。フォームの詳細タブを展開するとエラーの詳細を確認できます。

3.まとめ
今回の説明は以上です。間違い、指摘点などあればご指摘ください。
サービスエンドポイント構成時に指定したコントラクト(One-Way,Two-Way,Queue等)によっても動作の違いが発生するので詳細はSDKを確認してください。