Azure Function is a fantastic mechanism for various integration scenarios. Here are few key characteristics:
- Being a serverless application, Azure Function has the best time to market when it comes to deploying a web service
- Pay-per-use pricing model means you pay only for what you use
- Built in integration options in PowerApps and Flow allows you to give non-developers new building blocks when designing application and processes
- CORS (Cross-Origin Resource Sharing) support allows consuming Functions from server/client side in any domain you find suitable
What can you do with Azure Functions in the context of Microsoft Dynamics integration scenarios? Just about anything:
- Export/Import data to/from external application/data storage
- Notify an external application on a business event
- Get notification from external source
- Handle complex or lengthy computation process (considering the Plug-in/Custom Workflow Activity execution timeout limitation)
- Allow a 3rd party to interact with your Dynamics 365 organization without directly exposing an Dynamics endpoint and access credentials
So Why would you want to Execute Azure Function from an Entity Form?
- To provide responsive and fluent UX
- To avoid writing Plug-in/Custom Workflow Activity code which is not easy to debug
- To respond to form level events such as field value change as it occurs
In this post, I’ll walkthrough executing Azure Function from Microsoft Dynamics 365 Entity form. This walkthrough was set up with Microsoft Dynamics 365 v9.0 but can be easily adapted to v8.2.
My sample scenario demonstrates sending new Lead data to an Azure data storage using Azure Function. Off course, you can use this walkthrough with an On-premises deployment, but you will have to allow access to Azure.
Thank you Nishant Rana for an enlightening post that helped setup this walkthrough.
- Setup an Azure Function App
The Function App is a container that will contain your Functions and will allow you to manage these components.
In your Azure Portal (open a trial if you don’t have a subscription), type in ‘Function App’ in the search box and select the Function App item in the Marketplace.In the next dialog, type in the Function App name (which will be part of the function URL) and fill all other required fields.
Create a new Resource Group if you need one as well as Storage. Pin your function to the dashboard for easy access.After few seconds you will be navigated to the dashboard. Wait while your Function App is setup and then you will be navigated to the Function App design area
Click the Functions node and then click the ‘+ New function’ button to add a new Function
Select the Generic webhook option coupled with C# language
In the next dialog, give your function a meaningful name and click ‘Create’
Since we want to handle new Lead data by queuing it, click the Integrate node to add a new Function output and select the Azure Queue Storage.In the next dialog, note the Message parameter name as it will be part of the function code. Click ‘Save’
Click the Function node to access the Function code
Replace the existing code with the following code block and click ‘Save’
#r "Newtonsoft.Json" using System.Net; using Newtonsoft.Json; public class Lead { public string Topic { get; set;} public string FullName { get; set;} public string Email { get; set;} } //function entry point public static async Task Run(HttpRequestMessage req, TraceWriter log, IAsyncCollector outputQueueItem) { //trace incoming request log.Info($"New HandleNewLead request received"); //parse request into Lead object string jsonContent = await req.Content.ReadAsStringAsync(); var lead = JsonConvert.DeserializeObject(jsonContent); //trace Lead data log.Info($"Lead data: topic: {lead.Topic}, full name: {lead.FullName}, email: {lead.Email}"); // add lead object to queue await outputQueueItem.AddAsync(lead); //return response to the caller return req.CreateResponse(HttpStatusCode.OK, new { message = "Lead processed successfully" }); }
Opening the Logs pane below, you can see a successful compilation message
To test your Function, open the right pane and click the Test tab. Feed in some test JSON data and click Run. If all went well, you will receive a success message as well as a Function response
Clicking the Monitor node, you can see the queued test Lead data
Before leaving the Function area, click the Get function URL and copy it. You will be using it later in your Entity form JavaScript code
- Configure Cross-Origin Resource Sharing
In order to consume your Function from Microsoft Dynamics 365 organization which may be residing in a different domain, you’ll need to define it as an allowed origin under the CORS element:
Add your Microsoft Dynamics 365 organization base URL and click ‘Save’
- Setup entity form code and event
Head into your Microsoft Dynamics 365 organization and define a new JavaScript Web Resource named dyn_AzureServicesLib.js with the following code.
Replace the AZURE_BASE_ENDPOINT and AZURE_FUNCTION_ENDPOINT constants with the Function URL you copied earlier. Note the separation of the base URL part from the the function and code part(function (ns) { //constants Constants = function () { this.CALLING_MODULE_NAME = "dyn_AzureServicesLib.js"; this.AZURE_BASE_ENDPOINT = "https://dyneventhandlersample.azurewebsites.net/api/"; this.AZURE_FUNCTION_ENDPOINT = "HandleNewLead?code=xxx"; this.FORM_TYPE_CREATE = 1; this.MSG_OPERATION_SUCCESS = "Lead successfully exported :)"; this.MSG_OPERATION_FAILURE = "Something went wrong :("; return this; }(); //members var formContext = null; //public methods //Export newly created Lead record to external storage/application ns.exportLead = function (executionContext) { debugger //get form context formContext = executionContext.getFormContext(); //get form type var formType = formContext.ui.getFormType(); //operate for newly created records only if (formType == Constants.FORM_TYPE_CREATE) { //extract Lead details var lead = { firstname: formContext.getAttribute("firstname").getValue(), lastname: formContext.getAttribute("lastname").getValue(), email: formContext.getAttribute("emailaddress1").getValue() } //send Lead details to Azure Function executeAzureFunction(lead, handleExportSuccess, handleExportFailure); } } //private methods //handle opertation success handleExportSuccess = function (response) { formContext.ui.setFormNotification(MSG_OPERATION_SUCCESS, "INFO", null); } //handle opertation failure handleExportFailure = function (response) { formContext.ui.setFormNotification(MSG_OPERATION_FAILURE, "ERROR", null); } //execute Azure Function to process Lead details executeAzureFunction = function (lead, successHandler, failureHandler) { debugger //set Azure Function endpoint var endpoint = Constants.AZURE_BASE_ENDPOINT + Constants.AZURE_FUNCTION_ENDPOINT; //define request var req = new XMLHttpRequest(); req.open("POST", endpoint, 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) { req.onreadystatechange = null; if (this.status == 200) { successHandler(JSON.parse(this.response)); } else { failureHandler(JSON.parse(this.response).error); } } } //send request req.send(window.JSON.stringify(lead)); } })(window.AzureServicesLib = window.AzureServicesLib || {});
Next, go to the Lead entity form designer and add the dyn_AzureServicesLib.js Web Resource in the Form Properties dialog.
Bind the form OnSave event to the AzureServicesLib.exportLead function. Make sure you check the ‘Pass execution context…’ option.
Save and Publish. - Test
Create a new Lead record and save it. If all went well, you will see a form level success notification
Going back to your Function monitoring area, you should see your new Lead data queued successfully