How to use Promise to evaluate your ribbon enable rules with asynchronous Xrm.WebApi methods in Dynamics 365 Unified interface.

We all have been using Xrm.WebApi methods isn’t it? After all why not? They are wonderful. You no longer need to write lengthy XmlHttpRequests and parse the raw JSON results back. Xrm.WebApi methods does the hard part of converting the raw JSON outputs into strongly typed objects.

Not sure if anyone is still there but if you are new to Xrm.WebApi methods, below is the perfect link to get you started.

https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/xrm-webapi

Just when you are about to think that Xrm.WebApi methods have successfully ticked all the boxes, comes the ribbon rules. Xrm.WebApi rules are asynchronous.

Suppose you have a requirement where you need to show a ribbon button only if user is in role of Salesperson. For that we write one function “userHasRole” with the code below.

A cleaner way of doing this has been specified here.

 function userHasRole() {
       var isUserAdmin = false;
        Xrm.WebApi.retrieveMultipleRecords("role", "?$select=roleid&$filter=name eq 'salesperson'").then(
            function success(result) {
                 var userSettings = Xrm.Utility.getGlobalContext().userSettings;
                var securityRoles = userSettings.securityRoles;
                var isUserAdmin = false;
                 for (var i = 0; i < result.entities.length; i++) {
                     var roleId = result.entities[i].roleid;
                    if (securityRoles.indexOf(roleId) !== -1) {
                         isUserAdmin = true;
                        break;
                    }
                }
            },
            function (error) {
                console.log(error.message);
            }
         );
return isUserAdmin;
}

The problem with this code is very evident. Before Xrm.WebApi return the results, the function would return false. Off course you can modify this function using window level objects and then using Xrm.Page.ui.refreshRibbon() to refresh the ribbon after Xrm.WebApi returns results. And perhaps we all have done that so far.

While the above method would certainly work, you may think like – “Is this the only way?” Certainly not. If you are using Unified interface (only option left for online users after Oct 2020), you have a better way to achieve the same. Unified interface rules support returning a Promise rather than boolean for asynchronous rule evaluation. Let’s see how we can re-write the same rule using Promise. And surprisingly, many of us working in Dynamics 365 day in and day out are not aware of this.

JavaScript Promises 101
function userHasRole() {
    return new Promise(function (resolve, reject) {
        Xrm.WebApi.retrieveMultipleRecords("role", "?$select=roleid&$filter=name eq 'salesperson'").then(
            function success(result) {
                var userSettings = Xrm.Utility.getGlobalContext().userSettings;
                var securityRoles = userSettings.securityRoles;
                var isUserAdmin = false;
                for (var i = 0; i < result.entities.length; i++) {
                    var roleId = result.entities[i].roleid;
                    if (securityRoles.indexOf(roleId) !== -1) {
                        isUserAdmin = true;
                         break;
                    }
                 }
                // return true or false
                resolve(isUserAdmin);
            },
            function (error) {
                reject(error.message);
                console.log(error.message);
            }
        );
    });
}

Not much change in the code. All you need to do is return a Promise the resolve method of which should return true or false depending on business logic. And one very important to remember is that not only with Xrm.WebApi methods but also if you are using XMLHttp async, it would work just fine.

Put your code within the Promise and everything works like charm.

Any functionality before we implement, we should be aware of the limitations of the same. So we have a couple of them here.

  • This only works for Unified Interface. If you are using WebClient this won’t work
  • If the rule does not evaluate within 10 seconds, the rule would resolve with false value

Before I end, you may be asking where should can I find this in Microsoft documentation. Well if you are a big fan of Microsoft docs like me, here you go – https://docs.microsoft.com/en-us/dynamics365/customerengagement/on-premises/developer/customize-dev/define-ribbon-enable-rules#custom-rule

Hope this helps!

Debajit Dutta

(Dynamics MVP)

For consultation/ corporate training visit www.xrmforyou.com or reach out to us at info@xrmforyou.com

Our product offerings:

Role based views for Dynamics 365 (http://www.xrmforyou.com/role-based-views.html)

CRM-Sharepoint Attachment uploader and metadata manager (http://www.xrmforyou.com/sharepoint-integrator.html)

Record Cloner for Dynamics 365 (http://www.xrmforyou.com/record-cloner.html)

Multiselect picklist for Dynamics 365 (http://www.xrmforyou.com/multi-select-picklist.html)

3 thoughts on “How to use Promise to evaluate your ribbon enable rules with asynchronous Xrm.WebApi methods in Dynamics 365 Unified interface.”

  1. Hi Debajit,
    I am trying to show hide button using this approach and it is executing code as expected but not hiding the button when it returns false. Any suggestions as to what is causing this ?
    Thanks

      1. Hi Debajit,
        Here is the function :
        function ShowHideControl() {
        var globalContext = Xrm.Utility.getGlobalContext();
        var userRoles = globalContext.userSettings.securityRoles;
        return new Promise(function (resolve, reject) {
        Xrm.WebApi.retrieveMultipleRecords(EntityLogicalNames.Setting, “?$select=abcd,createdon&$orderby=xyz&$filter=contains(prqs, ‘” + requiredRole + “‘)”).then(function (result) {
        if (result.entities.length > 0) {
        var allowAction = false;
        var userData = result.entities[0].abcd;
        var userAllowedRoles = userData.split(‘~’);
        userRoles.forEach(function (item) {
        userAllowedRoles.some(function (element) {
        if (item.toLowerCase() === element.toLowerCase()) {
        allowAction = true;
        }
        });
        });
        resolve(allowAction);
        }
        }, function (error) {
        reject(error.message);
        });
        });
        }
        Just to let you know this function without promise is working fine and button works as expected. I am trying to hide the export contacts button on Advanced find.
        Thanks

Comments are closed.