Parse JSON string that represents the Dynamics 365 plugin execution context received in Azure Function

By | June 25, 2018

Introduction:

In our previous blogs of this Integrating Dynamics 365 with Azure Functions series, we have gone through a walkthrough with an example of creating an Azure Function and call the same through the following,

  1. An example of directly calling an Azure function from traditional workflows.
  2. Register as a WebHook and invoke it from a workflow.
  3. Register as a WebHook and register steps for messages that you would like the custom logic to be executed for.

In this blog, we will illustrate how to parse the JSON data that we received in the Azure Function. Let’s consider that we have registered a plugin step on an update of Account record which invoke a WebHook (here in our case Azure Function).

  • Read JSON data from the request body:

When plugin triggers and invokes a WebHook, three types of data received in the request i.e. Query String, Header Data and Request Body. The Request body contains a string that represents the JSON value of the RemoteExecutionContext class. This class defines the contextual information sent to a remote service endpoint at run-time. Below code snippet reads the content from the HttpRequestMessage and converts the received JSON string to proper deserializable JSON string.

using System.Text;
using System.Net;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request");

    string jsonContext = await req.Content.ReadAsStringAsync();
    log.Info("Read context: " + jsonContext);

    jsonContext = FormatJson(jsonContext);
    log.Info("Formatted JSON Context string: " + jsonContext);

    return req.CreateResponse(HttpStatusCode.OK, "Success");
}

/// <summary>
/// Function to convert the unformatted Json string to formatted Json string
/// </summary>
/// <param name="unformattedJson"></param>
/// <returns>string formattedJsonString</returns>
public static string FormatJson(string unformattedJson)
{
    string formattedJson = string.Empty;
    try
    {
        formattedJson = unformattedJson.Trim('"');
        formattedJson = System.Text.RegularExpressions.Regex.Unescape(formattedJson);
    }
    catch (Exception ex)
    {

        throw new Exception(ex.Message);
    }
    return formattedJson;
}

 

Remark: Add reference of System.Text so that we can use Regex expression.

 

  • Sample JSON string that gets received in the Azure Function:

Below is the sample JSON string.

{
  "BusinessUnitId": "f0bf3c9a-8150-e811-a953-000d3af29fc0",
  "CorrelationId": "39499111-e689-42a1-ae8a-5b14a84514ce",
  "Depth": 1,
  "InitiatingUserId": "df010dad-f103-4589-ba66-76a5a04c2a11",
  "InputParameters": [

    {
      "key": "Target",
      "value": {
        "__type": "Entity:http:\/\/schemas.microsoft.com\/xrm\/2011\/Contracts",
        "Attributes": [
          {
            "key": "telephone1",
            "value": "1111"
          },
          {
            "key": "accountid",
            "value": "ec4e2f7d-9d60-e811-a95a-000d3af24950"
          },
          {
            "key": "modifiedon",
            "value": "\/Date(1527757524000)\/"
          },
          {
            "key": "modifiedby",
            "value": {
              "__type": "EntityReference:http:\/\/schemas.microsoft.com\/xrm\/2011\/Contracts",
              "Id": "df010dad-f103-4589-ba66-76a5a04c2a11",
              "KeyAttributes": [],
              "LogicalName": "systemuser",
              "Name": null,
              "RowVersion": null
            }
          },
          {
            "key": "modifiedonbehalfby",
            "value": null
          }
        ],
        "EntityState": null,
        "FormattedValues": [],
        "Id": "ec4e2f7d-9d60-e811-a95a-000d3af24950",
        "KeyAttributes": [],
        "LogicalName": "account",
        "RelatedEntities": [],
        "RowVersion": null
      }
    }
  ],
  "IsExecutingOffline": false,
  "IsInTransaction": true,
  "IsOfflinePlayback": false,
  "IsolationMode": 1,
  "MessageName": "Update",
  "Mode": 0,
  "OperationCreatedOn": "\/Date(1527757530151)\/",
  "OperationId": "08fec203-ec78-4f7a-a024-c96e329a64fe",
  "OrganizationId": "b0714265-8e72-4d3b-8239-ecf0970a3da6",
  "OrganizationName": "org94971a24",
  "OutputParameters": [],
  "OwningExtension": {
    "Id": "3db800fe-0963-e811-a95a-000d3af24324",
    "KeyAttributes": [],
    "LogicalName": "sdkmessageprocessingstep",
    "Name": "D365WebHookHttpTrigger: Update of account",
    "RowVersion": null
  },
  "ParentContext": {
    "BusinessUnitId": "f0bf3c9a-8150-e811-a953-000d3af29fc0",
    "CorrelationId": "39499111-e689-42a1-ae8a-5b14a84514ce",
    "Depth": 1,
    "InitiatingUserId": "df010dad-f103-4589-ba66-76a5a04c2a11",
    "InputParameters": [
      {
        "key": "Target",
        "value": {
          "__type": "Entity:http:\/\/schemas.microsoft.com\/xrm\/2011\/Contracts",
          "Attributes": [
            {
              "key": "telephone1",
              "value": "1111"
            },
            {
              "key": "accountid",
              "value": "ec4e2f7d-9d60-e811-a95a-000d3af24950"
            }
          ],
          "EntityState": null,
          "FormattedValues": [],
          "Id": "ec4e2f7d-9d60-e811-a95a-000d3af24950",
          "KeyAttributes": [],
          "LogicalName": "account",
          "RelatedEntities": [],
          "RowVersion": null
        }
      },
      {
        "key": "SuppressDuplicateDetection",
        "value": false
      }
    ],
    "IsExecutingOffline": false,
    "IsInTransaction": true,
    "IsOfflinePlayback": false,
    "IsolationMode": 1,
    "MessageName": "Update",
    "Mode": 0,
    "OperationCreatedOn": "\/Date(1527757524631)\/",
    "OperationId": "08fec203-ec78-4f7a-a024-c96e329a64fe",
    "OrganizationId": "b0714265-8e72-4d3b-8239-ecf0970a3da6",
    "OrganizationName": "org94971a24",
    "OutputParameters": [],
    "OwningExtension": {
      "Id": "63cdbb1b-ea3e-db11-86a7-000a3a5473e8",
      "KeyAttributes": [],
      "LogicalName": "sdkmessageprocessingstep",
      "Name": "ObjectModel Implementation",
      "RowVersion": null
    },
    "ParentContext": null,
    "PostEntityImages": [],
    "PreEntityImages": [],
    "PrimaryEntityId": "ec4e2f7d-9d60-e811-a95a-000d3af24950",
    "PrimaryEntityName": "account",
    "RequestId": "08fec203-ec78-4f7a-a024-c96e329a64fe",
    "SecondaryEntityName": "none",
    "SharedVariables": [
      {
        "key": "ChangedEntityTypes",
        "value": [
          {
            "__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
            "key": "account",
            "value": "Update"
          }
        ]
      }
    ],
    "Stage": 30,
    "UserId": "df010dad-f103-4589-ba66-76a5a04c2a11"
  },
  "PostEntityImages": [],
  "PreEntityImages": [],
  "PrimaryEntityId": "ec4e2f7d-9d60-e811-a95a-000d3af24950",
  "PrimaryEntityName": "account",
  "RequestId": "08fec203-ec78-4f7a-a024-c96e329a64fe",
  "SecondaryEntityName": "none",
  "SharedVariables": [],
  "Stage": 40,
  "UserId": "df010dad-f103-4589-ba66-76a5a04c2a11"

}
  • Parse JSON string to ExpandoObject dynamic object:

Below code snippet deserialize the JSON string to dynamic ExpandoObject.

dynamic dynObj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonContext);

  • Read values from dynamic object:

Simple string type,

log.Info(“BusinessUnitId: ” + dynObj[“BusinessUnitId”]);

or

log.Info(“BusinessUnitId: ” + dynObj.BusinessUnitId);

Complex ParameterCollection type,

log.Info(“InputParameters->Target->LogicalName: ” + dynObj[“InputParameters”][0][“value”][“LogicalName”].ToString());

  • Parse JSON string to RemoteExecutionContext object:

Below code snippet deserialize the JSON string to RemoteExecutionContext object

#r "bin/Newtonsoft.Json.dll"
#r "bin/Microsoft.Xrm.Sdk.dll"
#r "bin/System.Runtime.Serialization.dll"

using System.Net;
using System.Dynamic;
using System.Text;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
   string jsonContext = await req.Content.ReadAsStringAsync();
   log.Info("Read context: " + jsonContext);

   jsonContext = FormatJson(jsonContext);
   log.Info("Formatted JSON Context string: " + jsonContext);

   Microsoft.Xrm.Sdk.RemoteExecutionContext remoteExecutionContext = DeserializeJsonString<Microsoft.Xrm.Sdk.RemoteExecutionContext>(jsonContext);
}

/// <summary>
/// Function to deserialize JSON string using DataContractJsonSerializer
/// </summary>
/// <typeparam name="RemoteContextType">RemoteContextType Generic Type</typeparam>
/// <param name="jsonString">string jsonString</param>
/// <returns>Generic RemoteContextType object</returns>
public static RemoteContextType DeserializeJsonString<RemoteContextType>(string jsonString)
{
    //create an instance of generic type object
    RemoteContextType obj = Activator.CreateInstance<RemoteContextType>();
    MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(jsonString));
    System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
    obj = (RemoteContextType)serializer.ReadObject(ms);
    ms.Close();
    return obj;
}

 

Remarks: Add reference of following dlls,

  • Xrm.Sdk.dll
  • Runtime.Serialization.dll

Below code snippet shows how to read values,

//read Plugin Message Name
    string messageName = remoteExecutionContext.MessageName;
    //read execution depth of plugin
    Int32 depth = remoteExecutionContext.Depth;
    //read BusinessUnitId
    Guid businessUnitid = remoteExecutionContext.BusinessUnitId;
    //read Target Entity 
    Microsoft.Xrm.Sdk.Entity targetEntity = (Microsoft.Xrm.Sdk.Entity)remoteExecutionContext.InputParameters["Target"];

    //read attribute from Target Entity
    string phoneNumber = targetEntity.Attributes["telephone1"].ToString();
    log.Info("Message Name: " + messageName);
    log.Info("BusinessUnitId: " + businessUnitid);
    log.Info("Plugin Depth: " + depth);

    log.Info("TargetEntity Logical Name: " + targetEntity.LogicalName);
    log.Info("Phone Number: " + phoneNumber);

 

Parse JSON

Conclusion:

The steps given above describes how to Parse JSON string that represents the Dynamics 365 plugin execution context received in Azure Function. To read all blogs about Azure Functions visit: http://bit.ly/inogic-azurefunctions

Cut short 90% of your manual work and repetitive data entry!

Get 1 Click apps and say goodbye to all repetitive data entry in CRM –
Click2Clone – Clone/Copy Dynamics 365 CRM records in 1 Click
Click2Export – Export Dynamics 365 CRM Report/CRM Views/Word/Excel template in 1 Click
Click2Undo – Undo & Restore Dynamics 365 CRM data in 1 Click