2つのDynamics CRM OnlineテナントをAzure Service BusおよびAzure Cloud Serviceと統合する

この記事では、Microsoft Azureを使用して2つのクラウドCRMシステムを統合した経験を共有したいと思います。 タスクの一環として、異なるOffice 365サブスクリプションにある2つのDynamics CRM Online実装間でメッセージを交換するシンプルなクラウドアプリケーションを構築する必要があります。メッセージを分析および処理するプロセスの役割。



会社の2つのブランチに重要で関連性のあるデータを提供する統合を実装する場合、システムの1つが要求に応答しない場合でも、メッセージを確実に配信する必要があります。 当然、この場合、接続やその他の問題がない場合にストレージと遅延配信を提供できるメッセージングシステムのサービスに頼ります。



マイクロソフトクラウドについて言えば、今日、企業はさまざまな製品を1つの同種のシステムに統合することに十分な注意を払っていることに注意してください。これにより、ソリューションの構築と展開のプロセスを簡素化および高速化し、いくつかの迷惑なミスを回避できます。



Dynamics CRMについて説明する場合、この製品はそのままでAzure Service Busとの連携をサポートします。これにより、1行のコードなしでデータをキューまたはセクションに送信できます。



1. Azure Service BusをDynamics CRM Onlineと連携するように構成します。



ここには特異性があります。 2つのシステム間の統合を構成するには、Service BusがCRMについて何かを知っている必要があり、CRMは適切なクラウドバスサービスを使用して正しく認証する必要があります。 現在、Dynamics CRMはACS(Azure Active Directory Access Control)による認証をサポートしています。 ACSの詳細については、次の記事をご覧ください。ACSとは

そのため、最初に行う必要があるのは実際にサービスバスを作成することです。これを使用してメッセージキューを操作しますが、残念ながらポータルを介して作成することはできません。 ACSを介して。 ACSをサポートするService Busを作成するには、Azure Power Shellを使用します。 Azure Power Shellとその使用方法の詳細については、次の記事をご覧ください: Azure PowerShellとは



[CmdletBinding(PositionalBinding=$True)] Param( # [Parameter(Mandatory = $true)] # [ValidatePattern("^[a-z0-9]*$")] [String]$Path = "q4depa2depb", # required needs to be alphanumeric [Bool]$EnableDeadLetteringOnMessageExpiration = $True , # optional default to false [Int]$LockDuration = 30, # optional default to 30 [Int]$MaxDeliveryCount = 10, # optional default to 10 [Int]$MaxSizeInMegabytes = 1024, # optional default to 1024 [Bool]$SupportOrdering = $True, # optional default to true # [Parameter(Mandatory = $true)] # [ValidatePattern("^[a-z0-9]*$")] [String]$Namespace = "sb4crm2crm", # required needs to be alphanumeric [Bool]$CreateACSNamespace = $True, # optional default to $false [String]$Location = "West Europe" # optional default to "West Europe" ) # Create Azure Service Bus namespace $CurrentNamespace = Get-AzureSBNamespace -Name $Namespace if ($CurrentNamespace) { Write-Output "The namespace [$Namespace] already exists in the [$($CurrentNamespace.Region)] region." } else { Write-Host "The [$Namespace] namespace does not exist." Write-Output "Creating the [$Namespace] namespace in the [$Location] region..." New-AzureSBNamespace -Name $Namespace -Location $Location -CreateACSNamespace $CreateACSNamespace -NamespaceType Messaging $CurrentNamespace = Get-AzureSBNamespace -Name $Namespace Write-Host "The [$Namespace] namespace in the [$Location] region has been successfully created." } $NamespaceManager = [Microsoft.ServiceBus.NamespaceManager]::CreateFromConnectionString($CurrentNamespace.ConnectionString); if ($NamespaceManager.QueueExists($Path)) { Write-Output "The [$Path] queue already exists in the [$Namespace] namespace." } else { Write-Output "Creating the [$Path] queue in the [$Namespace] namespace..." $QueueDescription = New-Object -TypeName Microsoft.ServiceBus.Messaging.QueueDescription -ArgumentList $Path $QueueDescription.EnableDeadLetteringOnMessageExpiration = $EnableDeadLetteringOnMessageExpiration if ($LockDuration -gt 0) { $QueueDescription.LockDuration = [System.TimeSpan]::FromSeconds($LockDuration) } $QueueDescription.MaxDeliveryCount = $MaxDeliveryCount $QueueDescription.MaxSizeInMegabytes = $MaxSizeInMegabytes $QueueDescription.SupportOrdering = $SupportOrdering $NamespaceManager.CreateQueue($QueueDescription); Write-Host "The [$Path] queue in the [$Namespace] namespace has been successfully created." }
      
      





私が使用したスクリプトのフルバージョンは、 ここから入手できます

このスクリプトは非常に簡単です。キューの作成に使用できるパラメーターについては、 Azure Service Bus-わかりました:パートII(キューとメッセージ)の記事で詳しく説明しています。 統合が正しく機能するには、レコードの作成と変更の両方のメッセージを処理するため、明確なメッセージシーケンスが必要であり、作成前にレコードの変更に関するメッセージを処理したくないことを付け加えます。 その結果、SupportOrderingフィールドを対応する値に入れることを忘れないでください。この場合、キューはFIFOの原則(先入れ先出し)に従って動作します。

画面でスクリプトが正常に完了すると、同様の結果が得られます。







これで、すべての準備が整った後、キューとバスが正しく作成され、ポータルで使用できることを確認できます。







2. Dynamics CRM OnlineをAzure Service Busに接続します。



したがって、Dynamics CRMをAzure Service Busに接続するには、プラグイン登録ツールを開き、CRMシステムとの接続を確立する必要があります。 プラグインリストが開いたら、[新しいサービスエンドポイントの登録と登録]を選択します。







次に、開いたウィンドウで、接続パラメーターを入力します。







名前はイベントの名前です。 例:ContactIntegration。

説明 -コールの説明。

ソリューション名前空間は、サービスバスの名前です。 私の場合:sb4crm2crm

パス -メッセージを受信するキューの名前。 私の場合:q4depa2depb

契約 -メッセージング契約。 いくつかのオプションがあります:キュー、トピック、一方向、双方向、REST。 キューとトピックを検討します。 これらの各契約の詳細については、次の記事をご覧ください。MicrosoftAzureソリューションのリスナーを作成します。 統合のために、永続キューを選択します。

要求 -メッセージのコンテキストの追加情報として、ユーザーIDを送信できます。

ID-作成された構成の一意の識別子。



すべてのフィールドに入力した後、ACSの構成に進むことができます。 これを行うには、[ACSの保存と構成]ボタンをクリックします。





管理キー -このキーは、Azureポータルから取得できます。 これを行うには、サービスタイヤセクションに移動します。



作成したバスを選択し、接続情報ボタンをクリックします。



ウィンドウが開き、必要な情報をすべて見つけることができます。



ACSセクションのデフォルトキーが必要です。

証明書ファイル -Azureとの統合のためにDynamics CRMを構成するために使用された公開証明書。

発行者名 -発行者名。 名前は、Azureとの統合のためにDynamics CRMを構成するために使用した名前と同じである必要があります。

証明書ファイルと発行者名は、Dynamics CRMの[設定]-> [カスタマイズ]-> [開発者リソース]にあります。 次のようになります。







証明書をダウンロードし、必要なすべてのフィールドに入力して、[ACSの構成]ボタンをクリックします。 すべてが正しく指定されている場合、しばらくしてから次のメッセージが表示されます。







その後、閉じるボタンをクリックしてウィンドウを閉じることができます。

次に、[認証の保存と検証]ボタンをクリックします。 次の形式のメッセージが表示されます。



認証の検証:成功



ウィンドウを閉じ、[保存]ボタンをクリックして完了です。

今では、処理してサービスバスに送信する特定のイベントを登録するだけです。

これを行うには、通常プラグインに対して行うように、プラグインステップを登録する必要があります。 連絡先エンティティの作成および更新メッセージを登録します。 これを行うには、新しく作成したサービスエンドポイントのコンテキストメニューを呼び出して、[新しいステップの登録]を選択します。 塗りつぶしは直感的です。





これで、作成した連絡先がService Busに送信されます。



Dynamics CRMからのメッセージ送信の成功または失敗を追跡するには、システムを開いて[設定]-> [システムジョブ]に移動します。 目的のエンティティを選択し、ビューをロードします。



以下は、潜在的なエラーを含むスクリーンショットです。





3.メッセージ処理のためのWorkerロールの開発。



メッセージを処理して別のシステムにアップロードし、潜在的なエラーに正しく応答するコードを開発するために、問題は小さいままです。

ワークフローはどこかで実行する必要があり、この場合はAzure Cloud Serviceです。

Visual Studioで新しいAzureクラウドサービスを作成しましょう。





さらに、Azure Cloud Serviceのコンテキストでは、Workerロールを作成することを示しています。







Azure Cloud ServiceとAzure Worker Roleができたので、キューからメッセージを受信できるコードを実装できます。 メッセージを受信する最も簡単な方法を以下に示します。

ワーカーロールには、OnStart、Run、およびOnStopの3つの必須メソッドが含まれています。 最も一般的な方法で実装を見てみましょう。 OnStartメソッドでは、バスに接続するためのパラメーターを決定します。ここで、データをアップロードする予定のシステムへの接続を開始することもできます。



  public override bool OnStart() { Trace.WriteLine("Creating Queue"); string connectionString = "*** provide your connection string here***"; var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString); //      Client = QueueClient.CreateFromConnectionString(connectionString, QueueName); return base.OnStart(); }
      
      







ここでは、キューからメッセージを受信するためにサブスクライブし、データを受信するためのメソッドを構成するため、Runメソッドが最も興味深いです。



  public override void Run() { OnMessageOptions options = new OnMessageOptions(); options.AutoComplete = true; //               receivedMessage options.MaxConcurrentCalls = 1; //         options.ExceptionReceived += LogErrors; //   // Start receiveing messages Client.OnMessage((receivedMessage) => //         { try { //    Trace.WriteLine("Processing Service Bus message: " + receivedMessage.SequenceNumber.ToString()); } catch { //        } }, options); CompletedEvent.WaitOne(); }
      
      







コードには十分に詳細なコメントが付いているため、ここでは他のことについてはコメントしません。

さて、そして最後に、OnStopメソッドがどのように見えるかを見てみましょう。



  public override void OnStop() { Client.Close(); CompletedEvent.Set(); //  Run  base.OnStop(); }
      
      





ここで、可能なすべての接続を閉じて、実行機能を終了します。 Azure Cloud Serviceの詳細については 、次の記事をご覧ください。MicrosoftAzure Cloud Servicesを使用した開発オプションの詳細な説明

また、作業ロールの公開は、ステージングとプロダクションの2つのバリエーションで実行できることに注意してください。 デバッグアセンブリタイプを使用してロールを公開すると、リリースアセンブリタイプを使用してから実稼働を使用すると、結果はステージング展開になります。 ロールが公開されてクラウドに配置されている場合でも、デバッグできます。 クラウドでの作業ロールの発行とデバッグの可能性についてさらに学習するには、次の記事を参照することをお勧めします。VisualStudioでのAzureクラウドサービスまたは仮想マシンのデバッグ



4. 2つのCRMシステムの統合のアーキテクチャ。



簡単に説明すると、キュー内のメッセージを処理するプロセスがどのように配置されるかを説明します。このプロセスは、2つのシステムの統合で開発および適用されました。 作業はCRMQueueProcessorクラスで始まり、その責任には、接続の初期化、「CrmMessageProcessor」メッセージプロセッサクラスの作成と構成、およびバスからのメッセージ受信のサブスクライブが含まれます。 初期化プロセス全体が完了し、処理が必要なメッセージがバスから受信されるとすぐに、CrmMessageProcessorが作業に入ります。

CrmMessageProcessorは、Observerパターンの実装です。 そのタスクは、システム内の変更を監視し、これらの変更についてサブスクライバーに通知することです。 必要な数のサブスクライバーが存在する場合がありますが、各サブスクライバーは自分へのメッセージを処理するかどうかを決定します。 すべてのサブスクライバーは、基本クラスCrmBaseIntegrationHandlerから継承されます。 抽象クラスであるCrmBaseIntegrationHandlerは、実装のためのいくつかのメソッドを提供します。



getProcessingEntityName() -オーバーライドする必要があり、エンティティの名前(連絡先など)を返します。



getProcessingAction() -オーバーライドする必要があり、ハンドラーが応答するアクションまたはアクションのセットを返します。 たとえば、これはレコードの作成です。



HandleCrmMessage(string entityLogicalNameValue、string requestNameValue、Entity entity) -メッセージ自体とアクションの本質とタイプを受け入れ、イベントが発生した場合にオーバーライドされたイベントハンドラーを呼び出します



Entity OnProcessCreateEntity(Entity sourceEntity) -レコードを作成するためのハンドラー。キューから取得したエンティティを取得し 、作成されるエンティティを形成します。



Entity OnProcessUpdateEntity(Entity sourceEntity) -レコードを変更するためのプロセッサ。キューから取得したエンティティを取得し 、変更されるエンティティを形成します。



  public class ContactIntegrationHandler : CrmBaseIntegrationHandler { public override string getProcessingEntityName() { return "contact"; } public override CrmMessageType getProcessingAction() { return CrmMessageType.Create | CrmMessageType.Update; } public override Entity OnProcessCreateEntity(Entity sourceEntity) { Entity output = new Entity("contact"); output["new_integrationid"] = sourceEntity.Id.ToString(); output["firstname"] = sourceEntity.GetAttributeValue<string>("firstname"); output["lastname"] = sourceEntity.GetAttributeValue<string>("lastname"); output["jobtitle"] = sourceEntity.GetAttributeValue<string>("jobtitle"); return output; } public override Entity OnProcessUpdateEntity(Entity sourceEntity) { Entity output = new Entity("contact"); output.Id = sourceEntity.Id; if (sourceEntity.Contains("firstname")) { output["firstname"] = sourceEntity.GetAttributeValue<string>("firstname"); } if (sourceEntity.Contains("lastname")) { output["lastname"] = sourceEntity.GetAttributeValue<string>("lastname"); } if (sourceEntity.Contains("jobtitle")) { output["jobtitle"] = sourceEntity.GetAttributeValue<string>("jobtitle"); } return output; } }
      
      





CrmMessageProcessorクラスは次のとおりです。

  public class CrmMessageProcessor { List<CrmBaseIntegrationHandler> integrationSubscribers; public CrmMessageProcessor(List<CrmBaseIntegrationHandler> subscribers) { this.integrationSubscribers = subscribers; } public void Subscribe(CrmBaseIntegrationHandler observer) { integrationSubscribers.Add(observer); } public void Unsubscribe(CrmBaseIntegrationHandler observer) { integrationSubscribers.Remove(observer); } public bool ProcessMessage(BrokeredMessage receivedMessage) { object entityLogicalNameValue, requestNameValue; ExtractCrmProperties(receivedMessage, out entityLogicalNameValue, out requestNameValue); if (entityLogicalNameValue == null || requestNameValue == null) { return false; } var context = receivedMessage.GetBody<RemoteExecutionContext>(); Entity entity = (Entity)context.InputParameters["Target"]; foreach (var handler in integrationSubscribers) { var status = handler.HandleCrmMessage((string)entityLogicalNameValue, (string)requestNameValue, entity); if (status.ProcessMessgae) { switch (status.MessageType) { case CrmMessageType.Create: { CrmConnector.Instance.CreateEntity(status.EntityToProcess); return true; } case CrmMessageType.Update: { var guid = CrmConnector.Instance.checkEntityForExistance(status.EntityToProcess); if (guid != Guid.Empty) { status.EntityToProcess.Id = guid; CrmConnector.Instance.UpdateEntity(status.EntityToProcess); return true; } break; } default: { break; } } } } return false; } /// <summary> ///    CRM   /// </summary> /// <param name="receivedMessage">   </param> /// <param name="entityLogicalNameValue">out:  </param> /// <param name="requestNameValue">out:  </param> private void ExtractCrmProperties(BrokeredMessage receivedMessage, out object entityLogicalNameValue, out object requestNameValue) { string keyRoot = "http://schemas.microsoft.com/xrm/2011/Claims/"; string entityLogicalNameKey = "EntityLogicalName"; string requestNameKey = "RequestName"; receivedMessage.Properties.TryGetValue(keyRoot + entityLogicalNameKey, out entityLogicalNameValue); receivedMessage.Properties.TryGetValue(keyRoot + requestNameKey, out requestNameValue); } }
      
      





ハンドラーのいずれもメッセージを処理しなかった場合、対応するマークの付いた未処理のメッセージのキューに置かれます。 メッセージの処理中にエラーが発生した場合、キュー内のメッセージのブロックを解除し、再度処理を試みます。そのため、試行の制限に達するまで、メッセージは未処理のメッセージのキューに入ります。 次に、CrmQueueProcessorクラスからの抜粋です。

 public void OnMessageRecieved(BrokeredMessage receivedMessage) { try { if (processor.ProcessMessage(receivedMessage)) receivedMessage.Complete(); else receivedMessage.DeadLetter("Canceled", "No event handler found"); } catch (Exception ex) { receivedMessage.Abandon(); logger.LogCrmMessageException(receivedMessage, ex); } }
      
      





生のメッセージキューからメッセージへのパスを取得するには、QueueClientオブジェクトの既存のインスタンスでFormatDeadLetterPathメソッドを呼び出し、ワークキューの名前を引数として渡す必要があります。

QueueClient.FormatDeadLetterPath(queueName)

この行が適切なパスを形成し、メッセージを受信して​​処理するために安全にサブスクライブできます。



5.結論



分析した例では、キューが使用され、すべてのメッセージが1つのワークフローで処理されます。 キューを使用する代わりに、セクション(トピック)を使用することもできます。同じ作業ロール内または異なるロール内の異なるスレッドによって異なるエンティティからのメッセージが処理されるように構成できます。 各サブスクライバーには独自のキューがあり、正しく構成されたフィルターは、このインスタンスで処理する必要があるメッセージのみを受信します。 同時に複数の作業ロールの作業を同期する必要がある場合は、このためにBlobリースを使用できます。Azureでの作業ロールの同期の詳細については、次の記事をご覧ください: 複数のロールインスタンスでのジョブの同時実行の防止



脚注が作成された記事のリスト:

ACSとは

Azure PowerShellとは何ですか?

PowerShellスクリプトを使用してサービスバスキュー、トピック、およびサブスクリプションを作成する方法

Azure Service Bus-理解したとおり:パートII(キューとメッセージ)

Microsoft Azure Cloud Servicesを使用した開発機会の詳細な説明

Visual StudioでAzureクラウドサービスまたは仮想マシンをデバッグする

複数のロールインスタンスでジョブが同時に実行されるのを防ぎます。






All Articles