In the previous post, we saw how we created the default template of durable function. In this post, we’ll modify that to do some CRUD operation in D365 CE.
For demo purpose, let’s pick a simple scenario i.e. Retrieve related records(accommodation payments) of a particular Contact record and update their field(Expected Refund Due Date a datetime field).
We’ll change the name of the functions(optional) and here’s the code for HTTP Triggered function which will trigger the orchestration function.
[FunctionName("AccommodationPayments")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Function, "get", "post")]HttpRequestMessage req,
[OrchestrationClient] DurableOrchestrationClient starter,
ILogger log)
{
//start the orchestration
string instanceId = await starter.StartNewAsync("AccommodationPayments_OrchestrationFunction", null);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
Below is the code for orchestration function which passes the contact ID as an input to the activity function.
[FunctionName("AccommodationPayments_OrchestrationFunction")]
public static async Task<string> RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context, ILogger log)
{
string contactID = "32B54F5A-95A6-E911-A857-000D3A372186";
return await context.CallActivityAsync<string>("AccommodationPayments_ActivityFunction", contactID);
}
Here’s the code for activity function which retrieves the related accommodation payment records of the contact record and updates their field.
[FunctionName("AccommodationPayments_ActivityFunction")]
public static async Task<string> RetrieveRecords([ActivityTrigger] string input, ILogger log)
{
string resultMessage = "Request processed successfully.";
var crmOrganizationUrl = Environment.GetEnvironmentVariable("CRMOrganization");
var crmOrganizationVersionUrl = Environment.GetEnvironmentVariable("CRMOrganizationVersionUrl");
var crmUrl = $"{crmOrganizationUrl}{crmOrganizationVersionUrl}";
var resultAccessToken = await CRMWebApiHelper.GetAccessToken();
log.LogInformation($"Access Token received");
var crmApi = new CRMWebAPI(crmUrl, resultAccessToken);
try
{
var recordId = input;
var dateEntered = DateTime.Now.AddDays(20);
var results = GetAccommodationPayments(crmApi, recordId).ToList();
log.LogInformation($"Got accommodation payments");
if (results == null || results.Count <= 0) return null;
foreach (var entity in results)
{
var accommodationPaymentDictionary = new Dictionary<string, object>();
accommodationPaymentDictionary.Add("dxc_expectedrefundduedate", dateEntered.Date.ToString("yyyy-MM-dd"));
var response = crmApi.Update("dxc_accommodationpayments", entity.accommodationPaymentId, accommodationPaymentDictionary).Result;
}
}
catch (Exception ex)
{
log.LogError($"Accommodation Payment Update Failed for Contact: {input} - StackTrace : {ex.StackTrace} - Exception Message : {ex.Message}");
resultMessage = ex.Message;
}
return resultMessage;
}
Below is the helper method to get the access token:
public static class CRMWebApiHelper
{
public async static Task<string> GetAccessToken(ILogger log)
{
var authority = Environment.GetEnvironmentVariable("AuthorityUrl");
var clientid = Environment.GetEnvironmentVariable("ClientId");
var crmBaseUrl = Environment.GetEnvironmentVariable("CRMOrganization");
var clientSecret = Environment.GetEnvironmentVariable("ClientSecret");
var tenantId = Environment.GetEnvironmentVariable("TenantId");
var clientCredential = new ClientCredential(clientid, clientSecret);
var authContext = new AuthenticationContext($"{authority}{tenantId}");
var authResult = await authContext.AcquireTokenAsync(crmBaseUrl, clientCredential);
return authResult.AccessToken;
}
}
We are getting Environment variables used above from local.settings.json file in the project as shown below:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"CRMOrganization": "https://<crmOrgName>.crm.dynamics.com",
"ClientSecret": "<clientSecret>",
"ClientId": "<clientID>",
"TenantId": "<tenantID>",
"AuthorityUrl": "https://login.microsoftonline.com/",
"CRMOrganizationVersionUrl": "/api/data/v9.1/"
}
}

Below is the helper method to get the related records of the contact record:
private static IEnumerable<AccommodationPaymentObject> GetAccommodationPayments(CRMWebAPI api, string contactId)
{
var xml = @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
<entity name='dxc_accommodationpayment'>
<attribute name='dxc_accommodationpaymentid' />
<attribute name='dxc_name' />
<attribute name='createdon' />
<order attribute='dxc_name' descending='false' />
<filter type='and'>
<condition attribute='dxc_clientid' operator='eq' uitype='contact' value='{" + contactId + @"}' />
</filter>
</entity>
</fetch>";
var options = new CRMGetListOptions()
{
FormattedValues = true,
FetchXml = xml
};
var result = api.GetList("dxc_accommodationpayments", options);
if (result == null || result.Result == null || result.Result.List.Count == 0)
return new List<AccommodationPaymentObject>();
return (from r in result.Result.List.Cast<IDictionary<string, object>>()
select new AccommodationPaymentObject
{
accommodationPaymentId = Guid.Parse(r["dxc_accommodationpaymentid"].ToString())
});
}
Use below mentioned namespaces for the above code written:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Xrm.Tools.WebAPI;
using Xrm.Tools.WebAPI.Requests;
Below is the helper class used:
public class AccommodationPaymentObject
{
public Guid accommodationPaymentId { get; set; }
}
Use the nuget packages shown below:

Now that we have written the code, let’s run the azure function from Visual Studio(by pressing F5) and wait for the command line interface for the below output with the URL of Azure function: http://localhost:7071/api/AccommodationPayments

Since the Azure function is not expecting any parameter to be sent, we can directly hit the above URL in browser. However, we’ll test the same using Postman app.
Before testing let’s see the records and their value in the system:

Now, let’s test using Postman. Put
Method: GET
URL: http://localhost:7071/api/AccommodationPayments
and click Send. After clicking Send, we can see that Status returned is: 202 Accepted which means the request has been accepted for further processing.

Durable Functions expose few other APIs as shown below which we’ll get after sending the request. One of them is used to get the status of the request as highlighted below:

Executing the statusQueryGetUri in Postman, we got the below output which shows that the request has been processed successfully(our custom message).

Now that the request has been processed successfully, checking the records and their value in the system we found that records have been updated successfully as shown below:

In the next post, we’ll see how we can parameterize the durable function and deploy to Azure.
Hope it helps !!
2 thoughts on “D365: Azure Durable Function with D365 CE – Part 2”