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

Advertisement

Logging and Handling Microsoft Dynamics CRM 2013 Exceptions – Part 3

In the previous post I described a solution to the business problem of logging & handling implementation level exceptions (presented in the first post of this series).
In this post, I will supply an actual solution, demonstrate common usage scenarios and other solution features.

Before I walkthrough usage scenarios, some implementation notes:

In order to support exceptions raised from transactional components (such as Plugin registered to pre/post operation stages), the LogException method in ExceptionManagement.cs file is using the ExecuteMultipleRequest class to execute the e4d_LogExceptionRequest request. As the ExecuteMultipleRequest instance is external to the Plugin transaction, it manages to create the Exception record without the Plugin transaction rolling it back. Using the Execute method directly with the e4d_LogExceptionRequest request instead, would have executed the LogException Action, but the created Exception record would have been deleted by the Plugin transaction rollback, leaving no trace.

The provided Exception Management solution is unmanaged. This allows you to modify the solution freely but note that unmanaged solution can not be uninstalled.

The provided Exception Management solution is exported as MSCRM2013 6.0 compatible.

As an Exception record is created on behalf of a user, all users must have the Create, Read and Write privileges for the Exception entity.
The solution contains a custom Security Role named Exception Logging Authorized which includes these privileges for the Exception entity. Make sure you add this role to your users or add the privilege to their existing roles.

Ok, let’s get some work done:

Step 1: import and configure the Exception Management solution:

1. Download and extract the E4D MSCRM2013 Excreption Management.zip package.
2. Import the solution ExceptionManagement_1_0_0_0_target_CRM_6.0.zip.  Leave the ‘Enable any SDK message…’ checkbox checked.
3. Add the Exception entity to the Settings area.
4. Publish the solution.
5. Add the Exception Logging Authorized Security Role to all users.

Step 2: Follow the relevant scenario below to use the Exception Management functionality from your custom code:

Scenario 1: Logging Exceptions from JavaScript code running in Entity form

1. Open the target entity Form Properties window.
2. In the Events tab, under Form Libraries, add the ExceptionManagement.js library to the form libraries.
3. If the JavaScript Web Resource that contains your custom code does not appear in the form libraries, add it. Make sure it appears below the e4d_ExceptionManagement.js library.

image_thumb5_thumb

4. In your custom JavaScript code (someFunction in my example), wrap the target code with Try-Catch blocks.
5. In the Catch block, add the following line of code. You can add your own error handling code in the Catch block.

Sdk.logException(error, Sdk.e4d_LogExceptionRequest.PriorityLevel.LOW);

Your code should look similar to this:

function someFunction()
{
try
{
//your custom business logic here
}
catch(error)
{
//your custom exception handling here
//log exception. This is the only line of code required in your custom code
//You can set the Priority level to MEDIUM, HIGH or CRITICAL instead of LOW
Sdk.logException(error, Sdk.e4d_LogExceptionRequest.PriorityLevel.LOW);
}
}

6. Save and publish the entity form.

Scenario 2: Logging Exceptions from JavaScript code running in HTML Web Resource

1. In your HTML Web Resource, add the following references in that order:

    <script src=”ClientGlobalContext.js.aspx” type=”text/javascript”></script>
<script src=”e4d_exceptionManagement.js” type=”text/javascript”></script>

2. Follow steps 4,5 in the Scenario 1. 3.
3. Save and publish the Web Resource

Scenario 3: Logging Exceptions from c# code running in Plugin/Custom Workflow Activity/.net application

1. Add the ExceptionManagement.cs file to your Visual Studio project
2. In your code, add the following using statement

   using ExceptionMgmtServices;

3. wrap the target code with Try-Catch blocks
4. In the Catch block, add the following line of code. You can add your own error handling code in the Catch block

string friendlyMessage = ExceptionManagement.LogException(ex, service, PriorityLevel.LOW);

Your code should look something like this:

try
{
//your custom business logic here
}
catch (Exception ex)
{
//your custom exception handling here
//You can set the Priority level to MEDIUM, HIGH, CRITICAL instead of LOW
string friendlyMessage = ExceptionManagement.LogException(ex, service, PriorityLevel.LOW);
//Optionally, throw out the exception to return friendly message to the client
throw new Exception(friendlyMessage);
}

I advise you test the relevant scenarios by generating an intentional exception, such as calling a non-existing function. Make sure an error message is displayed, an Exception record is created and that the Exception details are correct.

The ExceptionManagement_1_0_0_0_target_CRM_6.0.zip contains a Web Resource named ClientSideExceptionDemo.htm which demonstrate scenario 2.

The E4D MSCRM2013 Excreption Management package contains a sample Plugin project called E4D.CRM.ExceptionMgmtServices.ClientPlugin aimed to demonstrate scenario 3.

Managing Exceptions tools:

Navigate to the Exceptions node in the Settings area to view the Exceptions list. You can feed the unique token displayed in the error message in the Quick Find search box to quickly find the relevant Exception record.
You can export list of Exception records to Excel in order to send out to be reviewed by external developer.

image_thumb14_thumb[3][4]

image_thumb11_thumb[1][4]

image_thumb2_thumb[1]

 

As this solutions is unmanaged, you can can customize and extend it as you wish, here are some ideas:

  • Create a Workflow Rule that will add exceptions to a designated Queue from which Exceptions can be pulled and handled by developers or system administrators.
  • Create a Workflow Rule that will send email to administrators group when critical Exception record is created.
  • Create a Workflow Rule that will send an acknowledgment email to the end user when Exception record is created or handled (status change).
  • Develop a Custom Workflow Activity or Plugin component that will integrate with your ALM application to automatically send exceptions to be handled by the development team.
  • Add JavaScript code to display a nice error message using setFormNotification when error is displayed in entity forms, instead of displaying an alert.

I would like to hear your ideas about extending and improving this solution.

Microsoft Dynamics CRM 2013 Custom Action – A Single Implementation Point

Microsoft Dynamics CRM 2013 introduced a new member in the Processes family: Custom Actions.
I see great potential in this new feature, especially as it can be easily executed (synchronously or a-synchronously) from client side code and return results. While Synchronous Workflow can also be executed from client side code, it can’t return results in an elegant manner.

This Custom Action capability enables both server side and client side to share a single implementation point. Here is an example:

Lets say that whenever a Contact record is created, the government id number must be validated (9 digits that adhere to a specific algorithm). Contacts are created manually by users but also programmatically as part of a scheduled integration with an external application.

User experience considerations dictate a client side validation, as we want to warn the user about an invalid value when he changes the field value rather than when when saving the record.
But what about the records created programmatically? We have to add another point of implementation in the form of a Plugin or a Synchronous Workflow to support that.
So we end up with two implementation points, both JavaScript code and a server side component code. This means more development and maintenance efforts.

Let’s review a different solution approach using Custom Action:

  1. Develop a ‘IDValidation’ Custom Workflow Activity to preform the required validation and return a Boolean (valid/invalid) result.
  2. Wrap the IDValidation’  Custom Workflow Activity with a Custom Action and call the Custom Action from JavaScript onchange event (see Andrii’s useful solution here).
  3. Call the Custom Action directly from your server side integration code or wrap the Custom Workflow Activity with a Synchronous Workflow to support records created programmatically

This approach uses a single custom code component consumed both from server and client side, therefor reducing development and maintenance efforts.

Spot The Differences

It has been a long time since my last post. I have been busy completing some projects and receiving my first MVP award.

Now I am back. With a vengeance.

This week I have been giving 3 different workshops on Microsoft Dynamics CRM 2013 aspects, the last one was yesterday. I have been using an Online deployment in my workshops, specifically, Ireland data center (crm4) and also included some screenshots in my presentations. These screenshots allow me to spot some changes applied sometime last night…

 

Yesterday, the Sitemap looked like this:

image

This morning it looks like this:

Untitled

 

Note the Ribbon background color change and the added tiles reflection.  Another thing to note, is the Help icon change.

The forms also received slight changes : the  header tiles background is now white and on mouse over change to brown background. The Inline sub grid font seem to have changed as well.

image             image

 

Can you spot any more differences?