Writing Microsoft Dynamics 365 Plug-ins in Azure Functions

Over the years, as Microsoft Dynamics 365 has grown and matured, one thing has stayed pretty consistent: plug-ins. If you are unfamiliar with the concept, a plug-in is custom business logic triggered by registered events in the Dynamics 365 event pipeline (e.g., whenever a new account is created, a custom plug-in fires to execute our augmented logic).

With the advent of configurable webhooks in version 9.0 of the platform, it became rather simple to wire up a webhook—just like configuring a plug-in—that actually gets executed in an Azure Function. This might not make sense for a typical plug-in that implements internal logic for an implementation, but it might be an excellent option for when you run up against limitations of the plug-in model or are considering using plug-ins for integration.

This blog walks you through how to write a plug-in in Azure Functions. If you aren’t familiar with these concepts, take a few minutes and read more about Dynamics 365 Plug-ins and/or using webhooks in Dynamics 365 v9.0+.

Why use a webhook (Azure function or otherwise) instead of a plug-in?

So, why would you want to use a webhook rather than a plug-in? There are two main reasons:

Sandbox plug-in limitations – Those who are familiar with plugins in the Dynamics 365 environment (particularly online) are also aware that there are some common pain points as well:

  • No mechanism for ‘shared’ or ‘helper’ assemblies – must use ILMerge
  • Execute with partial trust
  • 2-minute timeout
  • Only HTTP/HTTPS protocols allowed
  • Cannot access network via IP (only DNS)
  • Resource usage/management: since plugins run inside the CRM resource pool, pushing resource-intensive tasks outside of CRM reduces overall load on the system

Integrations – In addition to the situations listed above, you might also want to keep integration logic out of the code base, and implement our integration logic outside of CRM. Integrations are also more complicated when there are existing .NET assemblies to facilitate integration with an external system, but they do not work in a partial trust environment (even if you are able to use ILMerge). In those cases, you end up having to write more complex plugin code since you can’t use the helper assemblies.

How to write a plug-in using a webhook

This process is simple. First, you create a service to consume the incoming webhook requests from the Dynamics 365 platform. Second, you configure/register a webhook step on the Dynamics 365 service using the Plug-in Registration tool. This is explained clearly in the documentation referenced at the top of the article, but we will review a simple example here as well.

Create and deploy a webhook service using an Azure Function

NOTE: This example uses Visual Studio 2017 with the Azure Development workload installed so I can code my Azure Functions directly in Visual Studio – create a new Azure Function, with type ‘Generic Webhook’.  

  1. Replace the template code inside the ‘Run’ method with something like the following to test with. (I called my function ‘WebhookSample’)

            log.Info($”Webhook was triggered!”);

            // get the context from D365

            string jsonContent = await req.Content.ReadAsStringAsync();

            // log what we got from D365 as an example

            log.Info(jsonContent);

            // return a success message to D365

            return req.CreateResponse(HttpStatusCode.OK);

  1. Deploy the Azure Function to your Azure Function app. Note the Function URL and Key for use in the next step. If you browse to the Function App in the Azure Portal, select your function, and next to the Run button you can ‘Get Function URL’. By default it will require a key – which you can cut/paste from the URL, or get from the ‘Manage’ section of your Azure Function where you manage the keys for this function.

Configure Dynamics 365 to call your new Azure Function webhook

    1. Open the Plug-in Registration tool and connect to your organization (https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/download-tools-nuget)
    2. Register a new webhook.

 

 

 

 

 

 

 

3. Tie the webhook to an event in Dynamics 365. Select the newly registered web hook, right click, and select ‘Register New Step’.

In this case, the webhook is set to execute whenever an update to an account record is made in Dynamics 365.

4. Click ‘Register New Step’ – then verify your webhook and step are registered successfully.

5. Verify that Dynamics 365 is triggering your Azure Function:

  • Modify an account record in D365
  • View the Azure Function’s log in the Azure Portal – you should see a log message containing the JSON that is being sent to Azure from D365.

How to use the information passed from Dynamics 365

Now that you have a giant JSON payload from Dynamics 365 – what do you do with it?

Luckily, you can take that JSON and turn it back into a RemoteExecutionContext object right out of the SDK, then work with that context almost exactly as though you were in plug-in code…

            RemoteExecutionContext cdsContext =  ContextHelper.GetContext(jsonContent);

…where the ‘GetContext’ method looks something like this. Just use the DataContractJsonSerializer to take the JSON and turn it back into a RemoteExecutionContext. (including the appropriate references):

        /// <summary>

        /// Returns an execution context object familiar to CRM developers

        /// </summary>

        /// <param name=”contextJSON”>JSON String</param>

        /// <returns>Xrm sdk RemoteExecutionContext</returns>

        public static RemoteExecutionContext GetContext(string contextJSON)

        {

            RemoteExecutionContext rv = null;

            using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(contextJSON)))

            {

                DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(RemoteExecutionContext));

                rv = (RemoteExecutionContext)ser.ReadObject(ms);

            }

            return rv;

        }

Now you have a context object that will look familiar to plug-in developers everywhere that you can use inside your Azure function.

            log.Info(“Received: ” + cdsContext.MessageName);

            Entity updatedAccount = (Entity)cdsContext.InputParameters[“Target”];

            log.Info(updatedAccount.LogicalName + “: ” + updatedAccount.Id);

     return req.CreateResponse(HttpStatusCode.OK);

Deploy your updated code and update another account in Dynamics 365, then check the logs. It should show you the message (Update) and the updated account ID in the log.

You are now able to pass the context of an event in the Dynamics 365 event pipeline directly to an external webhook service with ZERO code inside the Dynamics 365 platform—all you did in Dynamics 365 was register the webhook and link it to an event in the Dynamics 365 event pipeline via configuration using the Plug-in Registration Tool from the SDK (NuGet). Furthermore, you can interact with that provided context just as from within a native Dynamics 365 plug-in by deserializing the incoming context to a RemoteExecutionObject.

Sample code

Following is the code of a sample Azure Function in CRM/Dynamics 365 for Sales. NOTE: This is from a Visual Studio Function App project and will need to be tweaked if you want to use the code in a .csx file (Azure Functions that are edited from the Azure Portal UI instead of using Visual Studio):

using System.IO;

using System.Net;

using System.Net.Http;

using System.Runtime.Serialization.Json;

using System.Text;

using System.Threading.Tasks;

using Microsoft.Azure.WebJobs;

using Microsoft.Azure.WebJobs.Host;

using Microsoft.Xrm.Sdk;

namespace AKA.D365.CRM.AzureFunctions.Samples

{

    public static class WebhookSample

    {

        [FunctionName(“WebhookSample”)]

        public static async Task<object> Run([HttpTrigger(WebHookType = “genericJson”)]HttpRequestMessage req, TraceWriter log)

        {

            log.Info($”Webhook was triggered!”);

            // get the context from D365

            string jsonContent = await req.Content.ReadAsStringAsync();

            RemoteExecutionContext cdsContext = GetContext(jsonContent);

            log.Info(“Received: ” + cdsContext.MessageName);

            Entity updatedAccount = (Entity)cdsContext.InputParameters[“Target”];

            log.Info(updatedAccount.LogicalName + “: ” + updatedAccount.Id);

            // return a success message to D365

            return req.CreateResponse(HttpStatusCode.OK);      

        }

        /// <summary>

        /// Returns an execution context object familiar to CRM developers

        /// </summary>

        /// <param name=”contextJSON”>JSON String</param>

        /// <returns>Xrm sdk RemoteExecutionContext</returns>

        private static RemoteExecutionContext GetContext(string contextJSON)

        {

            RemoteExecutionContext rv = null;

            using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(contextJSON)))

            {

                DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(RemoteExecutionContext));

                rv = (RemoteExecutionContext)ser.ReadObject(ms);

            }

            return rv;

        }

    }

}

For more information on this process, or if you have questions around Dynamics 365 and Azure development,  contact us.

By | 2018-11-29T18:12:13+00:00 November 29th, 2018|Sales & Service (CRM), Tech Tips, Uncategorized|0 Comments
Alternative Text

Contributor: Ken Heiman

Ken Heiman is responsible for overseeing the technical excellence of all of AKA's CRM customer projects. He brings a decade of consulting, technical, and management expertise to his position.

Leave A Comment