In the fast-paced world of business applications, integration between different systems is crucial for achieving streamlined operations and delivering comprehensive solutions. Dynamics 365, a powerful suite for managing customer relationships, often needs to interact with external APIs to exchange data, automate workflows, and extend functionality. In this blog post, we’ll walk through the process of integrating Dynamics 365 with external APIs using custom actions and plugins, ensuring your applications are both functional and efficient.
Integrating Dynamics 365 with external APIs allows businesses to:
- Automate Data Exchange: Reduce manual data entry by automatically pushing or pulling data between Dynamics 365 and other systems.
- Enhance Functionality: Expand the capabilities of Dynamics 365 by leveraging services and features available through external APIs.
- Improve User Experience: Create seamless workflows that combine the strengths of multiple applications, offering a more cohesive experience.
Overview of the Integration Process:
1.Add the Button to the Ribbon (Command Bar): To initiate the integration, you’ll need to add a button labeled Send Details to
XYZ to the ribbon (command bar) of your Dynamics 365 form. This button will trigger the entire process. You can add the button using the Ribbon Workbench, a popular tool for customizing the Dynamics 365 apps. In this guide, we’ll use the Ribbon Workbench for simplicity.


2.Create the Custom Action: Next, create a custom action in Dynamics 365 that will be linked to the button you added in the previous step. This custom action will trigger the plugin that handles the API integration. You can create this action directly through the Dynamics 365 interface or using the Power Platform. This step is crucial because it serves as the bridge between the button click and the backend logic handled by the plugin.

3. Write the JavaScript to Trigger the Custom Action: To make the button functional, create a JavaScript Web Resource in Dynamics 365. This script will trigger the custom action whenever the button is clicked. Below is a sample JavaScript code that you can use. It retrieves necessary data (like the Opportunity ID), and then calls the custom action.
Here’s a sample JavaScript code that you can use:
function sendDetailsToXYZ (executionContext) {
var opportunityId = Xrm.Page.data.entity.getId().replace(/[{}]/g, "");
// console.log("Opportunity ID:", opportunityId);
var confirmStrings = {
text: "Do you want to create a client record in XYZ? Please confirm.",
title: "Confirmation Dialog",
confirmButtonLabel: "Confirm",
cancelButtonLabel: "Cancel"
};
var confirmOptions = {
height: 200,
width: 450
};
Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions).then(
function (success) {
if (success.confirmed) {
console.log("Running Action....");
var globalContext = Xrm.Utility.getGlobalContext();
var serverURL = globalContext.getClientUrl();
var actionName = "bh_CustomActionToPluginSendDetailsToVCC";
var data = {
"MyInputParam" : opportunityId
};
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)
{
console.log("Action Called Successfully...");
var result = JSON.parse(this.response);
var pluginResponse = result.MyOutputParam;
console.log(pluginResponse);
if(result.MyOutputParam == "Client record has been created in XYZ"){
var alertStrings = {
confirmButtonLabel: "OK",
text: `${pluginResponse}`,
title: "Success"
};
var alertOptions = {
height: 120,
width: 260
};
try {
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
} catch (error) {
console.error(`Error displaying alert: ${error.message}`);
}
}
else{
var errorAlertStrings = {
confirmButtonLabel: "OK",
text: `${pluginResponse}`,
title: "Error"
};
var alertOptions = {
height: 120,
width: 260
};
try {
Xrm.Navigation.openAlertDialog(errorAlertStrings, alertOptions);
} catch (error) {
console.error(`Error displaying alert: ${error.message}`);
}
}
}
else
{
var error = JSON.parse(this.response).error;
console.log("Error in Action: "+error.message);
}
}
};
//Execute request passing the input parameter of the action
req.send(window.JSON.stringify(data));
} else {
console.log("Dialog closed using Cancel button or X.");
}
}
);
}
4. Develop a Plugin to Handle the Logic: The plugin will be responsible for fetching data from Dynamics 365 and sending it to the external API. This ensures that the data exchange is secure, reliable, and efficient. The plugin is triggered when the custom action is executed, which is accomplished by creating a synchronous, post-operation, server-side step.
using System;
using System.Collections.Generic;
using System.IdentityModel.Protocols.WSTrust;
using System.Linq;
using System.Runtime.Remoting.Contexts;
using Microsoft.Xrm.Sdk;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Microsoft.Xrm.Sdk.Query;
namespace XYZCRMPlugin
{
public class SendDetailToXYZ : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
string environmentVariableSchemaName = "bh_vytaapiaccesskey";
// Retrieve the Environment Variable Definition record
QueryExpression Apiquery = new QueryExpression("environmentvariabledefinition")
{
ColumnSet = new ColumnSet("environmentvariabledefinitionid", "defaultvalue")
};
Apiquery.Criteria.AddCondition("schemaname", ConditionOperator.Equal, environmentVariableSchemaName);
EntityCollection envVarDefResults = service.RetrieveMultiple(Apiquery);
string apiKey = null; // Initialize the apiKey variable
string stateCodeValue = null;
if (envVarDefResults.Entities.Count > 0)
{
Entity envVarDef = envVarDefResults.Entities[0];
Guid envVarDefId = envVarDef.Id;
// Try to get the current value of the environment variable
QueryExpression valueQuery = new QueryExpression("environmentvariablevalue")
{
ColumnSet = new ColumnSet("value")
};
valueQuery.Criteria.AddCondition("environmentvariabledefinitionid", ConditionOperator.Equal, envVarDefId);
EntityCollection envVarValResults = service.RetrieveMultiple(valueQuery);
if (envVarValResults.Entities.Count > 0)
{
Entity envVarVal = envVarValResults.Entities[0];
apiKey = envVarVal.GetAttributeValue<string>("value");
}
// If no current value is found, use the default value
if (string.IsNullOrEmpty(apiKey))
{
apiKey = envVarDef.GetAttributeValue<string>("defaultvalue");
}
// Use the environment variable value as needed
tracingService.Trace($"Environment Variable Value: {apiKey}");
}
else
{
tracingService.Trace("Environment Variable Definition not found.");
}
var query = new QueryExpression("bh_systemsetting")
{
ColumnSet = new ColumnSet("bh_value")
};
query.Criteria.AddCondition("bh_name", ConditionOperator.Equal, "CREATE_CLIENT_XYZ_API_URL");
var apiUrlEntity = service.RetrieveMultiple(query).Entities.FirstOrDefault();
if (apiUrlEntity == null || !apiUrlEntity.Contains("bh_value"))
{
throw new InvalidPluginExecutionException("API URL not found in system settings.");
}
string apiUrl = apiUrlEntity["bh_value"].ToString();
tracingService.Trace($"API URL: {apiUrl}");
string opportunityId = (string)context.InputParameters["MyInputParam"];
tracingService.Trace($"OpportunityID: {opportunityId}");
if (!string.IsNullOrEmpty(opportunityId))
{
try
{
// Define the column you want to retrieve from opportunity
ColumnSet opportunityColumns = new ColumnSet("parentcontactid", "bh_quotestringfromxyz");
// Retrieve the opportunity record
Entity opportunity = service.Retrieve("opportunity", new Guid(opportunityId), opportunityColumns);
// Extract the 'parentcontactid' field from the opportunity record
EntityReference parentContact = opportunity.Contains("parentcontactid") ? (EntityReference)opportunity["parentcontactid"] : null;
string quoteString = opportunity.Contains("bh_quotestringfromxyz") ? opportunity["bh_quotestringfromxyz"].ToString() : string.Empty;
if (parentContact != null)
{
// Define the columns you want to retrieve from contact
ColumnSet contactColumns = new ColumnSet(
"firstname",
"lastname",
"emailaddress1",
"telephone1",
"address1_city",
"bh_stateid",
"address1_line1",
"address1_postalcode",
"address1_line2"
);
// Retrieve the contact record
Entity contact = service.Retrieve("contact", parentContact.Id, contactColumns);
// Extract the fields from the contact record
string firstName = contact.Contains("firstname") ? contact["firstname"].ToString() : string.Empty;
string lastName = contact.Contains("lastname") ? contact["lastname"].ToString() : string.Empty;
string email = contact.Contains("emailaddress1") ? contact["emailaddress1"].ToString() : string.Empty;
string telephone = contact.Contains("telephone1") ? contact["telephone1"].ToString() : string.Empty;
string city = contact.Contains("address1_city") ? contact["address1_city"].ToString() : string.Empty;
// string stateOrProvince = contact.Contains("address1_stateorprovince") ? contact["address1_stateorprovince"].ToString() : string.Empty;
string addressLine1 = contact.Contains("address1_line1") ? contact["address1_line1"].ToString() : string.Empty;
string postalCode = contact.Contains("address1_postalcode") ? contact["address1_postalcode"].ToString() : string.Empty;
string addressLine2 = contact.Contains("address1_line2") ? contact["address1_line2"].ToString() : string.Empty;
EntityReference stateId = contact.Contains("bh_stateid") ? (EntityReference)contact["bh_stateid"] : null;
if (stateId != null)
{
ColumnSet columnSet = new ColumnSet("bh_statename");
Entity stateEntity = service.Retrieve("bh_state", stateId.Id, columnSet);
if (stateEntity != null && stateEntity.Contains("bh_statename"))
{
stateCodeValue = stateEntity["bh_statename"].ToString();
}
}
// Trace the retrieved values
tracingService.Trace($"Parent Contact Details:");
tracingService.Trace($"First Name: {firstName}");
tracingService.Trace($"Last Name: {lastName}");
tracingService.Trace($"Email: {email}");
tracingService.Trace($"Telephone: {telephone}");
tracingService.Trace($"City: {city}");
tracingService.Trace($"State/Province: {stateCodeValue}");
tracingService.Trace($"Address Line 1: {addressLine1}");
tracingService.Trace($"Postal Code: {postalCode}");
tracingService.Trace($"Address Line 2: {addressLine2}");
tracingService.Trace($"Quote String: {quoteString}");
// Create the customer data
var customerData = new
{
crmID = opportunityId,
firstName = firstName,
lastName = lastName,
email = email,
phoneNumber = telephone,
quoteString = quoteString,
address = new
{
city = city,
province = stateCodeValue,
street = addressLine1,
unitNumber = addressLine2,
postalCode = postalCode,
aptType = "Apt"
}
};
// Wrap the customer data in an array
var dataToSend = new[]
{
new
{
customerData = customerData
}
};
// Call the asynchronous method to make the API request
var response = PostToAPIAsync(dataToSend, apiUrl, apiKey, tracingService , service, parentContact.Id).Result;
// Set the API response as the output parameter
context.OutputParameters["MyOutputParam"] = response;
}
else
{
tracingService.Trace("Parent contact ID is null.");
}
}
catch (Exception ex)
{
tracingService.Trace($"Exception: {ex.Message}");
throw;
}
}
else
{
tracingService.Trace("Opportunity ID is null or empty.");
}
}
private async Task<string> PostToAPIAsync(object data, string apiUrl, string apiKey, ITracingService tracingService , IOrganizationService service, Guid contactId)
{
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("x-api-key", apiKey);
client.DefaultRequestHeaders.Add("User-Agent", "VytaCRMPlugin");
string json = JsonConvert.SerializeObject(data);
HttpContent content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(apiUrl, content);
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
tracingService.Trace($"API call successful. Response: {result}");
// Deserialize the JSON response
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(result);
// Access the vccID from the deserialized object
string vccId = jsonResponse.vccID;
tracingService.Trace($"Extracted vccID: {vccId}");
if (!string.IsNullOrEmpty(vccId))
{
// Update the contact with the new vccId
Entity updateContact = new Entity("contact", contactId)
{
["bh_vcccontactid"] = vccId
};
// Update the contact record
service.Update(updateContact);
tracingService.Trace($"Contact updated with XYZ ID: {vccId}");
}
else
{
tracingService.Trace("XYZ ID was not returned from API.");
}
return "Client record has been created in XYZ "; // Return the response content
}
else
{
string error = $"API call failed. Status code: {response.StatusCode}, Reason: {response.ReasonPhrase}";
tracingService.Trace(error);
return $"There was an error occurred while creating client record in XYZ . Please contact administrator. Error Message:{error} "; // Return the error message
}
}
}
catch (Exception ex)
{
string exceptionMessage = $"Exception: {ex.Message}";
tracingService.Trace(exceptionMessage);
throw; // Optionally re-throw the exception if you want it to bubble up
}
}
}
}

Conclusion
Integrating Dynamics 365 with external APIs through custom actions and plugins is a powerful way to extend your CRM’s capabilities, automate workflows, and enhance user experiences. By following the steps outlined in this post, you can create seamless connections between Dynamics 365 and other systems, ensuring your business processes are as efficient and effective as possible.
Whether you’re looking to automate data entry, enhance functionality, or improve user experience, these integrations are key to unlocking the full potential of Dynamics 365. Start implementing these steps today and see the impact on your projects!
No comments:
Post a Comment