D365: Azure Durable Function with D365 CE – Part 3

In the previous post, we saw how we can implement durable function for CRUD operation in D365 and test it using Postman.
In this post we’ll see how we can parameterize durable function so that we can pass parameters from external system to durable function and process it further.

Let’s take a simple example as the previous post. We’ll update the related records(accommodation payments) of a contact record(record id passed as string parameter) and update their Expected Refund Due Date field(datetime value passed as string parameter). Let’s get started.

So, the code of HTTP triggered function goes like below and we have used AuthorizationLevel as Function:

        public static async Task<HttpResponseMessage> HttpStart(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post")]HttpRequestMessage req,
            [OrchestrationClient] DurableOrchestrationClient starter,
            ILogger log)
            var postedData = await req.Content.ReadAsStringAsync();
            log.LogInformation($"Posted Data = '{postedData}'.");

            if (string.IsNullOrWhiteSpace(postedData))
                return req.CreateResponse(HttpStatusCode.BadRequest, new { summary = "Posted data is empty" });

            string instanceId = await starter.StartNewAsync("AccommodationPayments_OrchestrationFunction", postedData);

            log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

            return starter.CreateCheckStatusResponse(req, instanceId);

The highlighted line above reads the parameter passed to the durable function.

The orchestration function code goes like this:

        public static async Task<string> RunOrchestrator(
            [OrchestrationTrigger] DurableOrchestrationContext context, ILogger log)
            var input = context.GetInput<string>();
            log.LogInformation($"Inside Orchestration");
            var data = JsonConvert.DeserializeObject<InputContext>(input);

            log.LogInformation($"Target Entity: {data.ContactID}. Target Record: {data.InputDate}");

            return await context.CallActivityAsync<string>("AccommodationPayments_ActivityFunction", data);

The helper object used for deserialization is as mentioned below:

 public class InputContext
        public string ContactID { get; set; }
        public string InputDate { get; set; }

Then, the code for activity function is as shown below:

        public static async Task<string> RetrieveRecords([ActivityTrigger] InputContext 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);
                var recordId = input.ContactID;
                var results = GetAccommodationPayments(crmApi, recordId).ToList();
                log.LogInformation($"Got accommodation payments");

                foreach (var entity in results)
                    var accommodationPaymentDictionary = new Dictionary<string, object>();
                    accommodationPaymentDictionary.Add("dxc_expectedrefundduedate", Convert.ToDateTime(input.InputDate).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.ContactID} - StackTrace : {ex.StackTrace}  - Exception Message : {ex.Message}");
                resultMessage = ex.Message;
            return resultMessage;

The helper method for getting related records is as shown below:

  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' />
                            <filter type='and'>
                              <condition attribute='dxc_clientid' operator='eq' uitype='contact' value='{" + contactId + @"}' />
                              <condition attribute='dxc_actualrefunddate' operator='null' />
            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())

Now that we are done writing code, let’s deploy it to Azure. Go to https://portal.azure.com/ and login with the azure subscription credential.
Click on Create a resource

Select Compute –> Function App

Give a unique name to function app, fill other details and click Next

Select Consumption plan type –> Click Next

Click Next

Click Next

Click Create

Once created, click Go to resource to view the details of the Azure function.

Let’s go to VS and try to deploy the azure durable function to Azure.
Right click on project –> Click Publish

Click New to create a new profile.

Select Azure functions Consumption plan on the left panel and click select existing –> Create Profile

Select the function app created in Azure and click OK

Then click Publish

It takes about a minute to get deployed and once deployed we will see the below message “Publish succeeded“.

Once published, go to Azure portal and we can see that all the functions have been deployed. Click on Function App settings.

Then click on Manage application settings.

Then Add application setting for each environment variable we have used in the code as shown below:

After adding all the application settings, click Save.

Then go to the function app –> Click on HTTP Triggered function and click Get Function URL

Copy the function URL. It’ll have a key added to the URL as a query string parameter since we have used AuthorizationLevel as Function for the HTTP triggered function written above.

Let’s check the value of the records in the system before testing the durable function.

Let’s go to Postman to test the azure durable function. Use
Method –> Post
URL –> Azure function URL copied above
In Body, let’s pass contact ID and date as input as below:

“ContactID”: “32b54f5a-95a6-e911-a857-000d3a372186”,
“InputDate”: “2020-11-15T12:00:00Z”

Then Click Send –> We can see that status is 202 Accepted and statusQueryGetUri is also exposed.

Hitting StatusQueryGetUri in a different tab we can see the response that Request Processed successfully

Now, going back to CE, we can see that the records have been updated with the date passed.

Going to Azure function and clicking on Monitor we can see the status of each request and logging that we have done which can be very helpful to troubleshoot any issue that we might come across.

In this post, we saw how we can parameterize azure durable function and deploy it to Azure. In the next post, we’ll see how we can register a webhook using plug-in registration tool for this azure durable function.

Hope it helps !!


6 thoughts on “D365: Azure Durable Function with D365 CE – Part 3

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.