Introduction

Navigating the intricate landscape of Microsoft Dynamics 365 CRM, understanding Actions becomes pivotal for enhancing operational efficiency and unlocking the platform's full potential. In this article, we delve into a specific example, unraveling the symbiotic relationship between UI and backend interactions with Actions, particularly focusing on data transmission in JSON format. As we embark on this exploration, we aim to provide a comprehensive understanding of how Actions empower businesses to streamline processes and adapt Dynamics 365 to their unique requirements.

What is Action in Microsoft D365?

An Action in Dynamics 365 is a centralized way to create business logic that can be invoked by triggering a registered event. Events can be triggered from a workflow or C# or JS code upon request. Actions accept input data, perform actions, and, if necessary, return output data. With Actions, we can extend the functionality of Dynamics 365 by creating customizable notifications. Out of the box, D365 provides a set of predefined events for each object (e.g., Create, Update, Delete, etc.). Creating an action means adding a user event for which input and output parameters can be defined if needed.

How to Trigger an Action

Actions can be triggered:

•    From a plugin using OrganisationRequest
•    From a user process
•    From a client application (Console App or other)
•    Using JavaScript

Description of the example

The example discussed here aims to understand one of the data transmission mechanisms when there's a need to use the same plugin for different purposes within the overall logic. Events will be added to change the fields Name and Surname for the configurable entity. The values of these fields will be passed to the Action, modified, and returned. Another event will be added to change the Max Value field; a random number will be generated depending on the value of this field. To avoid confusion, an additional parameter OperationType will be passed, determining which logic needs to be executed. Since there is no need to use transactions, disable the Enable Rollback parameter.

Create Action

Let's start by creating an Action. Go to Settings > Processes:

Click on New and select the type of Action. For Entity, select None (global) to not associate the call with a specific record and simplify the Action trigger:

Let's add the parameters:

•    RequestJson: input parameter used to pass data to the Action.
•    OperationType: determines the action to perform in the Action.
•    ResponseJson: contains the response from the Action.

Then press the Publish button to activate the Action.

Creating and Registering a Plugin for Action

Example plugin code in C#:

using Microsoft.Xrm.Sdk;
using System;

namespace UDS.TestAction
{
    public class TestAction : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            
            var operationType = context.InputParameters["OperationType"].ToString();

            var requestJson = context.InputParameters["RequestJson"].ToString();
            var json = Uri.UnescapeDataString(requestJson);


            string responseJson;
            switch (operationType)
            {
                case "GenerateFullName":
                    {
                        responseJson = GenerateFullNameAction(json);
                        break;
                    }

                case "GenerateRandomValue":
                    {
                        responseJson = GenerateRandomValueAction(json);
                        break;
                    }

                default:
                    {
                        throw new InvalidPluginExecutionException($"UDS.TestAction - invalid operation type {operationType}");
                    }
            }

            context.OutputParameters["ResponseJson"] = Uri.EscapeDataString(responseJson);
        }

        private string GenerateRandomValueAction(string json)
        {
            var request = JsonRepository.GetObjectFromJson<GenerateRandomValueRequest>(json);

            var random = new Random();
            int randomNumber = random.Next(1, request.MaxValue);

            return JsonRepository.GetJsonFromObject(new GenerateRandomValueResponse { 
                RandomValueMessage = $"Random value: {randomNumber}"
            });
        }

        private string GenerateFullNameAction(string json)
        {
            var request = JsonRepository.GetObjectFromJson<GenerateFullNameRequest>(json);

            var fullName = $"FullName from action - {request.FirstName}, {request.LastName}";

            return JsonRepository.GetJsonFromObject(
                new GenerateFullNameResponse {
                    FullName = fullName
            });
        }
    }

    public class GenerateFullNameRequest
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public class GenerateFullNameResponse
    {
        public string FullName { get; set; }
    }

    public class GenerateRandomValueRequest
    {
        public int MaxValue { get; set; }
    }

    public class GenerateRandomValueResponse
    {
        public string RandomValueMessage { get; set; }
    }
    
}

Example repository for working with JSON:

using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;

namespace UDS.TestAction
{
    public class JsonRepository
    {
        public static T GetObjectFromJson<T>(string jsonString)
        {
            var jsonFormatter = new DataContractJsonSerializer(typeof(T));
            var jsonData = Encoding.UTF8.GetBytes(jsonString);

            using (MemoryStream fs = new MemoryStream(jsonData))
            {
                T obj = (T)jsonFormatter.ReadObject(fs);

                return obj;
            }
        }

        public static string GetJsonFromObject<T>(T obj)
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                var serializer = new DataContractJsonSerializer(typeof(T));
                serializer.WriteObject(memoryStream, obj);

                return Encoding.UTF8.GetString(memoryStream.ToArray());
            }
        }
    }
}

Reopen it after completing the publishing action. Failure to do so will result in the new message being absent from the list of available events for registering a new plugin. Register the plugin using the Plugin Registration Tool:

 

Adding JS Code to Web Resource

JavaScript code from new_customentity.js:

var uds = uds || {};
uds.customentity = uds.customentity || {};
uds.customentity.form = uds.customentity.form || {};

(function (namespace)
{
	const actionName = 'new_TestAction';
	const OperationTypes = {
		GenerateFullName: 'GenerateFullName',
		GenerateRandomValue: 'GenerateRandomValue'
	}


	namespace.onChangeNameField = function (context) {
		var firstName = Xrm.Page.getAttribute('new_firstname').getValue();
		var lastName = Xrm.Page.getAttribute('new_lastname').getValue();

		if (firstName == null ||
			lastName == null)
			return;

		let json = {
			FirstName: firstName,
			LastName: lastName
		};

		callGlobalAction(json, OperationTypes.GenerateFullName)
			.then(function (e) {
				Xrm.Page.getAttribute('new_name').setValue(e.FullName);
			})
			.catch(function (error) {
				console.error(error);
			})
	}

	namespace.onChangeMaxValueField = function (context) {
		var maxValue = Xrm.Page.getAttribute('new_maxvalue').getValue();

		if (maxValue == null)
			return;

		let json = {
			MaxValue: maxValue
		};

		callGlobalAction(json, OperationTypes.GenerateRandomValue)
			.then(function (e) {
				Xrm.Page.getAttribute("new_randomvalue").setValue(e.RandomValueMessage);
			})
			.catch(function (error) {
				console.error(error);
			})
	}

	var callGlobalAction = function (json, operationType) {
		return new Promise(function (resolve, reject) {
			var serverURL = Xrm.Page.context.getClientUrl();

			var data = {
				"RequestJson": encodeURIComponent(JSON.stringify(json)),
				"OperationType": operationType
			};

			var req = new XMLHttpRequest();
			req.open("POST", serverURL + "/api/data/v9.2/" + actionName, true);
			req.setRequestHeader("Accept", "application/json");
			req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
			req.setRequestHeader("OData-MaxVersion", "4.0");
			req.setRequestHeader("OData-Version", "4.0");

			req.onreadystatechange = function () {
				if (this.readyState == 4 /* complete */) {
					req.onreadystatechange = null;
					if (this.status == 200 || this.status == 204) {
						var result = JSON.parse(this.response);
						var responseJson = decodeURIComponent(result.ResponseJson)
						var response = JSON.parse(responseJson);

						resolve(response);
					} else {
						var error = JSON.parse(this.response).error;
						reject("Error in Action: " + error.message);
					}
				}
			};

			req.send(window.JSON.stringify(data));
		});
	};
})(uds.customentity.form);

Adding Events for Field Changes

Registration of events for First Name and Last Name fields:

Similarly, for Max Value, we register an event on the change of this field and call the function uds.customentity.form.onChangeMaxValueField.

Calling Action from JavaScript

Let's fill in the values for First Name and Last Name, triggering a call to Action from JS code. Then, we'll fill in the value for Max Value, resulting in calling the same Action but with a different JSON type and a different value for the OperationType parameter.

Content of the request for the GenerateFullName event:

The content of the response for the event GenerateFullName:

The content of the request for the event GenerateRandomValue:

The contents of the response for the event GenerateRandomValue:

Conclusion

The seamless integration of Actions within Dynamics 365 CRM not only enhances system flexibility but also empowers developers to create tailored solutions that align precisely with business needs. Insights from this discussion underscore the significant impact Actions have on boosting operational agility and driving transformative change within organizations. As businesses continue their digital evolution, leveraging the capabilities of Actions becomes a strategic imperative, propelling them toward sustained growth and competitiveness in today's dynamic marketplace.

Separate Actions are recommended for different operations. However, when complex logic is needed from various parts of the CRM, adopting this approach proves beneficial. Based on experience, this method has proven effective, especially when developing a UI application with over 40 backend operations within a heavily used system supporting at least 400 users. Remember to disable transaction usage if it is unnecessary. Alternatively, utilize two Actions (with and without transactions) registered for the same logic and avoid using transactions for prolonged mass operations to prevent system blockages.