Sunday, March 26, 2023

Microsoft Dynamics 365 CRM - Azure integration + Service Bus + Azure Queue + Azure-aware plug-in + Queue Listener Class

Azure integration with Microsoft Dynamics 365 (online/ on-premises) 

 

Note: This article was created in 2017, however re-published in 2023. So, screens and navigation might vary little, but process is moral less same. 

 

You can connect Microsoft Dynamics CRM Online/ On-Premises with Microsoft Azure by coupling the CRM event execution pipeline to the Microsoft Azure Service Bus. Once configured, this connection allows data that has been processed as part of the current Dynamics CRM operation to be posted to the Azure Service Bus. Microsoft Azure Service Bus solutions that are Dynamics CRM-aware can listen for and read the Microsoft Dynamics CRM data from the service bus. 

This connection between Microsoft Dynamics CRM and the Microsoft Azure platform provides a secure and reliable channel for communicating Dynamics CRM run-time data to external cloud-based line-of-business (LOB) applications. 

Microsoft Dynamics CRM to Service Bus scenario     

The sequence of events as identified in this diagram are as follows: 

  1. A listener application is registered on a Microsoft Azure Service Bus solution endpoint and begins actively listening for the Microsoft Dynamics CRM remote execution context on the service bus. 

  1. A user performs some operation in Microsoft Dynamics CRM that triggers execution of the registered OOB plug-in or a custom Azure-aware plug-in. The plug-in initiates a post, through an asynchronous service system job, of the current request data context to the service bus. 

  1. The claims posted by Microsoft Dynamics CRM are authenticated. The service bus then relays the remote execution context to the listener. The listener processes the context information and performs some business-related tasks with that information. The service bus notifies the asynchronous service of a successful post and sets the related system job to a completed status. 

Prerequisites: 

  1. Microsoft Azure subscription or Azure Account 

  1. Microsoft Dynamics CRM 2016 Online instance (Trial is also fine) 

Get a Microsoft Azure subscription or a Trial account. (https://portal.azure.com). Login with any existing Work or Personal Microsoft Account. Work or school, or personal Microsoft account Work or school, or personal Microsoft account. 

 

  1. Setup of Azure Account:  

  Login to Azure account and follow below steps:  

  1. Create Resource Group:    

  1. Create Service Bus 

  1. Create Queue. 

 

  Navigate to Resource Group: Create Resource Group:    

   

Add new resource group + and enter below details: 

Resource Group Name: MSDCRMResourceGroupGanesh (as you like, enter any string), Subscription: Free Trial/ any paid subscription and Location: as your choice and click on Create button. 

 

Create Service Bus: Open the newly created Resource group and click on + icon and create new Service Bus.  

 

 

Enter the details of Create Service bus and click on Create button. Name: MSDCRMAzureGanesh.servicebus.windows.net 

 

Create Queue: Navigate to created Service Bus and open the Service Bus and click on + Queue. 

A queue contract provides a message queue in the cloud. With a queue contract, a listener doesn’t have to be actively listening for messages on the endpoint. For queues, there is a destructive read and a non-destructive read. A destructive reader reads an available message from the queue and the message is removed. A non-destructive read doesn’t remove a message from the queue. 

 

Enter the required attribute data to create a new Service Bus Queue; you may change Max Size, message time and lock duration as required; I used default values. Queue Name: MSDCRMAzureQueueGanesh 

 

Now your Service Bus Queue is ready. The next step is to register plugin step for service endpoint or write custom Azure-aware plugin which will connect your CRM to Azure.  

Microsoft Dynamics CRM SDK provides sample plugin code to pass CRM message onto the Azure Queue.  

..\SDK\SampleCode\CS\Azure\Plug-ins\SandboxPlugin.cs 

 

 

  1. Service Endpoint Registration in CRM:  

A service endpointrepresents a Microsoft Azure platform endpoint. This entity stores the configuration information of a service endpoint. The schema name for this entity is Service Endpoint.  

Connect the CRM instance using the Plugin Registration Tool and once connected click on register new service endpoint to your dynamics CRM instance as shown below. Plugin Registration Tool is available in dynamics CRM SDK. Download here 

Enter the Azure Service Bus Portal connection string. The Azure Service Bus connection string can be found in Azure portal under Service Bus properties > Connection Strings 

 

 

Click on RootManagerSharedAccessKey to open Connection Strings Section. Copy complete “CONNECTION STRING- PRIMARY KEY” and paste it to plugin registration tool.  

 

 

Click Next on the plugin registration tool where Azure service bus connection string added 

The new window will appear with queue details from your Azure Service Bus.  

Check Queue Name and leave the rest as it is and click on Save. And a new endpoint will be added. 

 

You will now see that the endpoint will be visible under your list of plugin assemblies. The next step would be to make a note of the endpoint Id that will be generated once your service endpoint is successfully registered. 

Under the properties of the service end point in plugin tool you will be able to find the endpoint Id as shown below 

Endpoint Id: c3d03e00-51e9-e611-8112-c4346bddb001  

 

 

There are two ways to register a step which will handle the messaging with Service Bus.  

  1. CRM OOB plugin:   

  1. Writing your custom Azure-aware plugin.  

CRM OOB plugin:   

Select the service bus endpoint choose Register and Register a New Step. This step will be for creation of an Account entity in CRM, so for the Message useCreate and for the Primary Entity use account. Set the Execution Mode to Asynchronous and click the Register New Step button. The endpoint uses an out of the box CRM Plug-In to handle the messaging with the Azure Service Bus. However, if you need more control over Azure Service Bus messaging you can create a custom Azure-aware plugin. 

 

 

  1. Write a custom Azure-aware plug-in: 

Writing a plug-in that works with Microsoft Azure is similar to writing any other Microsoft Dynamics CRM plug-in. However, in addition to invoking any desired web service methods, the plug-in must include code to initiate posting the execution context to the Microsoft Azure Service Bus. 

Step to create Plug-in:  

1. Open Visual Studio -> New project -> Visual C# -> Class Library 

2. Add CRM DLL References: Microsoft.Xrm.Sdk.dll & Microsoft.Crm.Sdk.Proxy.dll & .net assembly (System.Runtime.Serialization & System.ServiceModel.dll) 

3. Extend IPlugin Interface in Plug-in class 

4. Sign-in the plug-in with SNK (strong name key) 

5. Implement public void Execute(IServiceProvider serviceProvider) method 

6. Write your code and build the solution and register the plug-in assembly with plug-in registration tool. 

 

Open Visual studio and create new project: 

 

//Complete code of CustomAzureAwarePlugin.cs 

using System; 

using System.Collections.Generic; 

using System.Linq; 

using System.Text; 

using System.Threading.Tasks; 

 

using Microsoft.Xrm.Sdk; 

 

namespace CustomAzureAwarePlugInGanesh 

{ 

    /// <summary> 

    /// A custom plug-in that can post the execution context of the current message to the Windows 

    /// Azure Service Bus. The plug-in also demonstrates tracing which assist with 

    /// debugging for plug-ins that are registered in the sandbox. 

    /// </summary> 

    /// <remarks>This sample requires that a service endpoint be created first, and its ID passed 

    /// to the plug-in constructor through the unsecure configuration parameter when the plug-in 

    /// step is registered.</remarks> 

    public class CustomAzureAwarePlugin : IPlugin 

    { 

        private Guid serviceEndpointId; 

        public CustomAzureAwarePlugin(string config) 

        { 

            if (String.IsNullOrEmpty(config) || !Guid.TryParse(config, out serviceEndpointId)) 

            { 

                throw new InvalidPluginExecutionException("Service endpoint ID should be passed as config."); 

            } 

        } 

        public void Execute(IServiceProvider serviceProvider) 

        { 

            // Retrieve the execution context. 

            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); 

 

            // Extract the tracing service. 

            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); 

            if (tracingService == null) 

                throw new InvalidPluginExecutionException("Failed to retrieve the tracing service."); 

 

            IServiceEndpointNotificationService cloudService = (IServiceEndpointNotificationService)serviceProvider.GetService(typeof(IServiceEndpointNotificationService)); 

            if (cloudService == null) 

                throw new InvalidPluginExecutionException("Failed to retrieve the service bus service."); 

 

            try 

            { 

                tracingService.Trace("Posting the execution context."); 

 

                string accountName = string.Empty; 

                Entity entity = (Entity)context.InputParameters["Target"]; 

                if (entity != null) 

                { 

                    accountName = entity.GetAttributeValue<string>("name"); 

                } 

 

                string response = cloudService.Execute(new EntityReference("serviceendpoint", serviceEndpointId), context); 

                if (!String.IsNullOrEmpty(response)) 

                { 

                    tracingService.Trace("Response = {0}", response); 

                }                 

                tracingService.Trace("Done. Account Name: " + accountName); 

            } 

            catch (Exception e) 

            { 

                tracingService.Trace("Exception: {0}", e.ToString()); 

                throw; 

            } 

        } 

    } 

} 

 

While registering the plugin step you will have to pass the secure and unsecured config ID nothing but the endpoint id that is generated once you register the service endpoint. 

 

In my example I have registered the plugin on accounts creation. Whenever a new record is created the execution data will be sent to azure service bus queue under the active messages. 

 

  1. Read Messages (Data) from Azure Queue:  

To read data or messages from Azure Queue there are couple of approaches available.  

Here I am using C# Console Application to read data from Azure Queue. 

 

Steps are:  

  1. Create C# Console Application:  

Open Visual Studio -> New project -> Visual C# -> Select Console Application. 

Name the application as you like; I used: ConsoleApplicationCRMAzureOutBound  

 

 

  1. Add Microsoft Service Bus Reference into the project 

To add these reference you will have to Visual Studio -> Tools -> Library Package Manager -> Package Manager Console 

Run:  

PM> Install-Package WindowsAzure.ServiceBus 

PM> Install-Package Microsoft.WindowsAzure.ConfigurationManager -Version 2.0.2 (I used specific    version, remove version to use latest ConfigurationManager version) 

 

 

Or: Visual Studio -> Project -> Manage NuGet Packages > search for WindowsAzure.ServiceBus and Install (Same for ConfigurationManager assembly) 

 

 

 

Add CRM DLL References into project solution: Microsoft.Xrm.Sdk.dll & Microsoft.Crm.Sdk.Proxy.dll (These assemblies are available in CRM SDK)  

 

By default an App.config file will be created into the project. We will use this Config file to add our Azure Service Bus settings under the appSettings tag as shown below. Service Bus connection string and Queue Name.  

 

 

You need to put Azure Service Bus connection string of the Azure Service Bus Endpoint: Service Bus properties > Connection Strings. Refer Point 2 Service Endpoint Registration. And Queue Name: MSDCRMAzureQueueGanesh 

 

  1. Write a queue listener Class:  

Add new Class let’s call this class QueueListener.cs inside a new folder created under project QueueListener which will read any active messages posted into Azure Queue. Reference here. https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-get-started-with-queues  

 

 

Add below required namespaces using tags for the Azure Service Bus and Dynamics CRM reference assemblies:  

using Microsoft.ServiceBus.Messaging; 

using Microsoft.WindowsAzure;  

 

//Complete code of QueueListener Class 

using System; 

using System.Collections.Generic; 

using System.Linq; 

using System.Text; 

using System.Threading.Tasks; 

 

using Microsoft.ServiceBus.Messaging; 

using Microsoft.WindowsAzure; 

 

namespace ConsoleApplicationCRMAzureOutBound.QueueListener 

{ 

    public static class QueueListener 

    { 

        public static void Start() 

        { 

            //Create Azure Service Bus Connection 

            string connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString"); 

            string queueName = CloudConfigurationManager.GetSetting("QueueName"); 

            QueueClient Client = QueueClient.CreateFromConnectionString(connectionString, queueName); 

 

            //Specifies the OnMessageOptions options with which to instantiate the message pump. 

            OnMessageOptions options = new OnMessageOptions(); 

            options.AutoComplete = false; 

            options.AutoRenewTimeout = TimeSpan.FromMinutes(1); 

 

            //Callback method to invoke when the operation is complete to handle received messages. 

            //Syntax: QueueClient.OnMessage(Action<BrokeredMessage>, OnMessageOptions) 

            Console.WriteLine("Initiating Queue Listener..."); 

            Client.OnMessage((message => { 

                try 

                { 

                    Console.WriteLine("Calling Callback Method from QueueClient.OnMessage..."); 

                    ProcessMessagesManager pm = new ProcessMessagesManager(); 

                    pm.ProcessMessages(message); 

                    //Completes processing of a message and Remove message from queue. 

                    message.Complete(); 

                } 

                catch (Exception ex) 

                { 

                    Console.WriteLine("QueueListener Error: "+ex.ToString()); 

                    //Discards the message and relinquishes the message lock ownership. 

                    message.Abandon(); 

                } 

            }), options); 

        } 

    } 

}   

 

//End of QueueListener Class 

 

  1. Queue Processor manager:  

Using above code we will read the messages from Azure Queue. Since the plugin step we created will post the execution context parameters to the Azure Queue and we can access them in our application. Also we may filter the entity name and the message of the request it was invoked.  

 

We now have the context data with the parameters which is being read from the Queue. Next step is to pass the data and process it. In my scenario I am just reading the created account data and display in console.  

 

To do this added a new class ProcessMessagesManager.cs into same console application. This class will process the execution brokered messages and perform the required operation. Below code block also perform segregation of entity messages.  

 

//Complete code of ProcessMessagesManager.cs 

using System; 

using System.Collections.Generic; 

using System.Linq; 

using System.Text; 

using System.Threading.Tasks; 

 

using Microsoft.ServiceBus.Messaging; 

using Microsoft.Xrm.Sdk; 

 

namespace ConsoleApplicationCRMAzureOutBound 

{ 

    public class ProcessMessagesManager 

    { 

        public void ProcessMessages(BrokeredMessage message) 

        { 

            Console.WriteLine("Inside ProcessMessages method..."); 

            //Filter message with specific message properties. i.e. EntityLogicalName and RequestName 

            string entityLogicalNameValue = "account"; 

            string requestNameValue = "Create"; 

 

            RemoteExecutionContext context = message.GetBody<RemoteExecutionContext>(); 

            if (context != null) 

            {  

                 if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity) 

                 { 

                     var currentEntity = context.InputParameters["Target"] as Entity; 

                     Console.WriteLine("currentEntity.LogicalName: " + currentEntity.LogicalName + " & currentEntity.MessageName: " + context.MessageName); 

                     if(currentEntity.LogicalName==entityLogicalNameValue && requestNameValue==context.MessageName) 

                     { 

                         Console.WriteLine("Account Entity Create Message..."); 

                         Account.CreateAccountSubscriber objAcc = new Account.CreateAccountSubscriber(); 

                         objAcc.Execute(context); 

                     } 

                 } 

            } 

        } 

    } 

} 

 

 

  1. Subscriber Class: This class (CreateAccountSubscriber.cs inside an Account folder) get the execution context from Queue Processor manager and display the result into console. I have split into separate class. Here you can have your internal service or application to feed the data into your system.  

 

Final Solution Explorer 

    

//Complete code of CreateAccountSubscriber.cs:  

using System; 

using System.Collections.Generic; 

using System.Linq; 

using System.Text; 

using System.Threading.Tasks; 

 

using Microsoft.Xrm.Sdk; 

 

namespace ConsoleApplicationCRMAzureOutBound.Account 

{ 

    public class CreateAccountSubscriber 

    { 

        public void Execute(RemoteExecutionContext context) 

        {  

            Entity entity = (Entity)context.InputParameters["Target"]; 

            if (entity != null) 

            { 

                string accountName = entity.GetAttributeValue<string>("name"); 

                string emailaddress1= entity.GetAttributeValue<string>("emailaddress1") != null ? entity.GetAttributeValue<string>("emailaddress1") : ""; 

                string telephone1 = entity.GetAttributeValue<string>("telephone1") != null ? entity.GetAttributeValue<string>("telephone1") : ""; 

 

                Console.WriteLine("Account Created in CRM and pushed to Azure to Customer Application. "); 

                Console.WriteLine("Account name: " + accountName + ", emailaddress1: " + emailaddress1 + ", telephone1: " + telephone1); 

            } 

        } 

    } 

} 

 

  1. Main Class: Program.csAdd below lines to start the Queue Listener. 

//Complete code of Program.cs:  

using System; 

using System.Collections.Generic; 

using System.Linq; 

using System.Text; 

using System.Threading.Tasks; 

 

namespace ConsoleApplicationCRMAzureOutBound 

{ 

    class Program 

    { 

        static void Main(string[] args) 

        { 

            try 

            { 

                Console.WriteLine("Running Queue Listener"); 

                QueueListener.QueueListener.Start(); 

                Console.ReadKey(); 

            } 

            catch (Exception ex) 

            { 

                Console.WriteLine("Exception in Main Program "+ ex.ToString()); 

                Console.ReadKey(); 

            } 

        } 

    } 

} 

 

Test the console application:  

  1. Create a new account record into CRM.  

 

 

  1. Check Queues messages into Azure Service Bus: 1 new Messages.  

 

 

  1. Build the console application and run it. Output Result: