Walkthrough: Execute Azure Function from Microsoft Dynamics Entity Form

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.

 

  1. 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.

    Select Function App

    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.

    Define Function App

    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

    Function App design area

    Click the Functions node and then click the ‘+ New function’ button to add a new Function

    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

    Select the Generic webhook option coupled with C#  language

    In the next dialog, give your function a meaningful name and click ‘Create’

    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.

    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’

    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

    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

    Opening the Logs pane, you can see a success 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

    To test the function, open the right pane and click the Test tab. Feed in some test JSON data and click Run

    Clicking the Monitor node, you can see the queued test Lead data

    Clicking the Monitor node, you can see the queued 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

    Before leaving the Function area, click the Get function URL and copy it

  2. 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:

    In order to consume your Function from Microsoft Dynamics 365 organization which may be residing in a different domain, you need to define it as an allowed origin under the CORS element

    Add your Microsoft Dynamics 365 organization base URL and click ‘Save’

    Add your Microsoft Dynamics 365 organization base URL and click ‘Save’

  3. 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.

    add the dyn_AzureServicesLib.js Web Resource in the Form Properties dialog

    Make sure you check the ‘Pass execution context…’ option

  4. Test

    Create a new Lead record and save it. If all went well, you will see a form level success notification

    Create a new Lead record. If all went well, you will see a form success notification

    Going back to your Function monitoring area, you should see your new Lead data queued successfully

    Going back to your Function monitoring area, you should see your new Lead data queued successfully

Entity Scope Business Rules demystified

Entity scoped Business Rules run on both client and server side. On the server side, Business Rules are executed synchronously when a record is saved (created or updated) and this means that in some cases, Business Rules can replace synchronous Plug-ins. Business Rules UX is better and maintenance wise, implementing business logic using the declarative Business Rule is preferred over Plug-in custom code.  

Sounds great, but for some Business Rule actions the meaning of running on the server side is a bit obscure:

  1. Show error message: on client side, the save operation is prevented by the error message. Is the save operation also blocked on the server side? Does the caller receive the designated error message?
  2. Set business required: does the save operation fail if a value is not supplied for the required field on server side?
  3. Lock or unlock field: does this action actually prevent setting field value on server side?

As for the other actions, I assume the Set visibility action is meaningless on server side and also that Set field/default value actions work on both client and server side in the same manner.

To test these three actions on the server side, I created and updated an Account record via the OrganizationService API in a synchronous manner while activating only the relevant Business Rule. Following are the various tests and results:

  1. Show error message

    Business Rule definition:

    Show error message action test 

    Client side test:

    Show error message action client test

    Server side test result: Account record is not created, the caller receives the designated error message

    Show error message action server test

  2. Set business required:

    Business Rule definition:

    Set business required action test

    Client side test:

    Set business required action client test

    Server side test result: Account record can not be created without specifying a value for required field

    Set business required action server test

  3. Lock or unlock field

    Business Rule definition:

    Lock or unlock field action test

    Client side test:

    Lock or unlock field action client test

    Server side test result: Account record is created successfully although the Account Number field is set to be locked by the Business Rule

    Lock or unlock field action server test

Conclusion: Show error message and Set business required actions perform on server side similar to a Plug-in. The Lock or unlock field action is meaningless on server side.

Entity JavaScript Scaffold

In the last couple of years I have been writing more JavaScript code than Server side code in my Microsoft Dynamics CRM projects. Better UX, plethora of supporting JavaScript libraries, easier deployment and maintenance made me prefer client side solutions.

I usually create a JavaScript library for each entity requiring client side functionality (forms, command bars etc.).
Over time, I have formed a JavaScript class scaffold. As this scaffold has improved my productivity when it comes to developing, debugging and maintaining client side code, I would like to share it here.

Consistently using this or any other scaffold, especially in large teams/projects, can significantly lower the cost of maintenance as developers quickly learn to find the right location for new code or debugging breakpoint, even in code they have never seen before.

A few key concepts implemented by this scaffold:

  1. Using Namespace

    Using namespace help avoiding collisions with other functions or variables in the global namespace. External solutions may contain code which is beyond your control and so it is important to protect your code from collisions that can be hard to detect and debug.
    Using namespace also allow encapsulating private functions and variables while exposing public elements.

    image

    In this example, I am using ContactServices as my namespace. Consuming any public function from this class (e.g. form onLoad event handler) will use the namespace as prefix: ContactServices.HandleFormLoadEvent.
    The ns alias is used to attach public functions to the namespace.

  2. ‘Constants’ management
    Managing strings and magic numbers in a designated container lowers maintenance efforts as it eliminates the need to search for these values all over the code when a change is required.
    The const keyword can be used, but I am not sure it is supported by all browsers.

    image

  3. Generic event handlers
    The scaffold generic event handlers match the entity form events. I like to think of these as managing functions which doesn’t perform actual work (like any good manager) but activates the relevant worker functions.

    The ns prefix exposes the generic event handler as ‘public’  functions which can be wired to the form /attribute events.
    In my sample here, I would use ContactServices.HandleControlChangeEvent to launch the generic onChange event handler. Note that some of these generic event handler require context, so either check this in the event definition form or extract with code if not supplied by caller.

    Why should you use generic event handlers?
    Attaching event handlers via form is simplified since the event handling function name never changes.
    This is especially useful with attributes onChange event which is usually wired directly to event handling function.
    The following generic event handler extract the initiating attribute name from the context and use it to direct the event to the actual handler. This way, all onChange event handlers can be managed in one known location.
    The generic event handler also handles exceptions and frees the actual event handlers from this task.  This way, no exception can escape your catch block.

    image

  4. Public/Private elements
    Private variables and functions are used inside the namespace scope and are not exposed to external consumers.
    This allows exposing just the API you intend to and simplify matters for external consumers.
    Functions and variables which are prefixed with ns are exposed outside the namespace, other are invisible.
    To access a ‘public’ variable inside the namespace code, use this keyword.

    image

The full JavaScript scaffold code can be download here.
The scaffold is not wired to any specific Microsoft Dynamics CRM version. These concepts can be applied down to 2011 version at least.

If you have any comments or improvements for this scaffold, I would like to hear about it.  Share it here.

Color Your Entity

I have noticed a new attribute named ‘Color’ in the new entity form (Online, 2015 RU 0.1).
It is not documented in the online help yet, but guessing this attribute purpose is not difficult: customize the entity panel color in the navigation menu for custom and existing (OOTB) entities. Nice!

The Color attribute input text length is limited to 7 characters, to match a color Hex code such as #FF6600 (Orange). Setting a Hex code will let you preview the represented color in the preview box beside the attribute text box.

You can find any color Hex code in web site like this.

Setting entity color

Custom entity color in navigation menu