Friday 28 July 2017

How to tackle Concurrent Business Process flows

Dynamics 365 has introduced the new feature of Concurrent Business Process Flows. Here is a couple of good articles on that:



Even though this is called a feature I have found it as quite troublesome so far. It’s true that in some organizations different departments would need to run different processes on the same entity in which case this makes sense. But how if we want to make sure that only one BPF should be active at a time for a record? Right now we can’t do it without writing some fair amount of coding. Ideally Microsoft should have given the option to turn on or off the concurrent business process flows either at Organisation level or Entity level.


One of my clients had this requirement of having multiple BPFs for an entity but only one should be active at a time; i.e. everybody in the business unit should follow that process.

If user A has started the BPF1 and let’s say user B come and switch the Process  to BPF2.
As Alex had mentioned here I used a plugin to achieve this. Inside the plugin I get the GUIDs of all the users in the Business Unit, then create the IOrganizationService object using each User GUID

OrganizationService UserService = serviceFactory.CreateOrganizationService(userid);

and execute a SetProcessRequest with that IOrganizationService instance to set the business process flow in the backend.

SetProcessRequest req = new SetProcessRequest();
req.Target = new EntityReference(“EntityLogicalName”, recordGuid);
req.NewProcess = new EntityReference("process", GuidOfTHeBusinessProcessFlow);
var resp = UserService.Execute(req);


This way you can make sure the same Business Process Flow is used by all users.
But please note that each user should have write privileges on the entity to execute the SetProcessRequest. If not it throws an exception. So if all the users does not have write privileges on the entity you can provide the privilege temporarily dynamically in the plugin itself and once the code is executed you can revert.

UpdateRole(serviceContext, service, "Security Role name", PrivilegeDepth.Local);


public void UpdateRole(XrmServiceContext serviceContext, IOrganizationService service, string roleName, PrivilegeDepth depth)
{
            var securityRole = serviceContext.RoleSet.Where(ro => ro.Name == roleName).FirstOrDefault();
            var roleRequest = new RetrieveRolePrivilegesRoleRequest { RoleId = securityRole.Id };
            var roleResponse = service.Execute(roleRequest);

            var privilegeQuery = new QueryExpression { EntityName = "privilege", ColumnSet = new ColumnSet(true) };

            var privileges = service.RetrieveMultiple(privilegeQuery);
            var entityPrivileges = privileges.Entities.ToList()
            .Where(p => p.GetAttributeValue<string>("name").ToLower()
            .Contains("entityLogicalName"))
            .ToList();

            foreach (var priv in entityPrivileges)
            {

                if (priv.Attributes.Contains("name") && priv.Attributes["name"].ToString() == "prvWrite"+ "entityLogicalName")
                {
                    AddPrivilegesRoleRequest addPrivilegesRequest = new AddPrivilegesRoleRequest
                    {
                        RoleId = securityRole.Id,
                        Privileges = new[]
                        {                           
                            new RolePrivilege
                            {
                                PrivilegeId = priv.Id,
                                Depth=depth
                            }
                        }
                    };
                    service.Execute(addPrivilegesRequest);
                    break;
                }
            }
}


Friday 2 June 2017

How to assign a record to the current user via JavaScript

Recently I got a requirement from the customer to create a ribbon button on a custom entity so that a user can click on that button and get the record assigned to him/herself. You might wonder why create a ribbon button when we have the “Assign” button clearly. Reason is we need to do some web service calls to external systems too when user click on that button; so we needed a custom ribbon button.


I could achieve this easily by creating a JavaScript function like below to update the owner id and call this function on click of the ribbon button.


function AssignRecordToCurrentUser() {   
    var currentRecordId = Xrm.Page.data.entity.getId().replace("{", '').replace("}", '');
    var currentUserId = Xrm.Page.context.getUserId().replace("{", '').replace("}", '');       

    var entity = {};
    entity["ownerid@odata.bind"] = "/systemusers(" + currentUserId + ")";   
    var req = new XMLHttpRequest();
    req.open("PATCH", Xrm.Page.context.getClientUrl() + "/api/data/v8.2/new_workitems(" + currentRecordId + ")", false); // my entity name is new_workitem
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");   
    req.onreadystatechange = function () {
        if (this.readyState === 4) {
            debugger;
            req.onreadystatechange = null;
            if (this.status === 204) {
                //Success - No Return Data - Do Something
            } else {
                Xrm.Utility.alertDialog(this.statusText);
            }
        }
    };
    req.send(JSON.stringify(entity));
} 

This works fine as long as the current user has got the write privileges to the entity. If the current user has read only rights this throws an exception. So when I was searching the web I found that we need to pass an additional parameter and impersonate the call. So I used the GUID of a system administrator like below.

function AssignRecordToCurrentUser() {
    var currentRecordId = Xrm.Page.data.entity.getId().replace("{", '').replace("}", '');
    var currentUserId = Xrm.Page.context.getUserId().replace("{", '').replace("}", '');

    var entity = {};
    entity["ownerid@odata.bind"] = "/systemusers(" + currentUserId + ")";
    var impersonateUserId = "A734DA29-7993-DE11-BECE-005056B47DB0";// GUID of the system administrator
    var req = new XMLHttpRequest();
    req.open("PATCH", Xrm.Page.context.getClientUrl() + "/api/data/v8.2/new_workitems(" + currentRecordId + ")", false);// my entity name is new_workitem
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.setRequestHeader("MSCRMCallerID", impersonateUserId);
    req.onreadystatechange = function () {
        if (this.readyState === 4) {
            debugger;
            req.onreadystatechange = null;
            if (this.status === 204) {
                //Success - No Return Data - Do Something
            } else {
                Xrm.Utility.alertDialog(this.statusText);
            }
        }
    };
    req.send(JSON.stringify(entity));
}

But this did not work either. L

So I came up with another idea!!
I created an action to assign the record and then called that action from the JavaScript function. Here in this action I am passing the current user as an input parameter to the action.

My action looks like below:
















This is the parameter:














And the JavaScript to call the action is like this:

function AssignRecordToCurrentUser () {
    debugger;
    var currentRecordIdString = Xrm.Page.data.entity.getId();
    var currentRecordId = currentRecordIdString.replace("{", '').replace("}", '');
    var currentUserId = Xrm.Page.context.getUserId().replace("{", '').replace("}", '');
    var entityName = "new_workitem";

    ////using action
    debugger;
    var parameters = {};
    var currentuser = {};                                      
    currentuser.systemuserid = currentUserId;
    currentuser["@odata.type"] = "Microsoft.Dynamics.CRM.systemuser";
    parameters.CurrentUser = currentuser;

    var req = new XMLHttpRequest();
    req.open("POST", Xrm.Page.context.getClientUrl() + "/api/data/v8.2/new_workitems(" + currentRecordId + ")/Microsoft.Dynamics.CRM.new_Assignworkitemtocurrentuser", false);
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.onreadystatechange = function () {
        if (this.readyState === 4) {
            req.onreadystatechange = null;
            if (this.status === 204) {
                debugger;
                //Success - No Return Data - Do Something
            } else {
                debugger;
                Xrm.Utility.alertDialog(this.statusText);
            }
        }
    };
    req.send(JSON.stringify(parameters));
}

Make sure you activate your action.
I hope this will help you!

Friday 21 April 2017

How to read configuration data in plugins using secure configurations?


Recently, we deployed some custom SharePoint integration with Dynamics CRM which required some custom coding. As usual, we wanted to connect to different Dynamics CRM environments (DEV, TEST, UAT, PROD, etc.). Every time we deployed, we didn’t want to change the code so that it can connect to the correct SharePoint site.

To achieve this there are few options available and we used the Secure Configuration feature available with Plugin Registration tool. There you provide all the configurations like site URLs, usernames, passwords, etc., in XML format and you can access them within the constructor of your plugin.

Your plugin registration will look like this; inside secure configurations box you enter your configurations in xml format.
















Your configurations XML would look like this:

<Settings>
  <setting name="siteURL">
    <value>https://xxxx.sharepoint.com/sites/CRM-DEV/Cases/</value>
  </setting>
  <setting name="DocumentLibName">
    <value>CaseDocumentLibrary</value>
  </setting>
  <setting name="SPOUserName">
    <value>SPCRMIntegrationUser@xxxxx.com.au</value>
  </setting>
  <setting name="SPOpassword">
    <value>xxxxx</value>
  </setting>
  <setting name="Case_DocLibPath">
    <value>/sites/CRM-DEV/Cases/CaseDocumentLibrary</value>
  </setting>
  <setting name="Case_Type_Hidden_StaticName">
    <value>g5dc149027cf444eae73dfec7bd07885</value>
  </setting>
  <setting name="ContentTypeId">
    <value>0x012000E8517D690891E149A88FC509298C39B300BA883DF9E383BA47AD69DC71A7712B76</value>
  </setting>
</Settings>

Here in the constructor of the plugin, you can read the settings you set in the Secure Configuration XML.

public OnUpdateOfCase(string unsecureConfig, string secureConfig)
{
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(secureConfig);
            siteURL = SecureConfigHelper.GetConfigDataString(doc, "siteURL");
            DocumentLibName = SecureConfigHelper.GetConfigDataString(doc, "DocumentLibName");
            SPOUserName = SecureConfigHelper.GetConfigDataString(doc, "SPOUserName");
            SPOpassword = SecureConfigHelper.GetConfigDataString(doc, "SPOpassword");
            Case_DocLibPath = SecureConfigHelper.GetConfigDataString(doc, "Case_DocLibPath");
            Case_Type_Hidden_StaticName = SecureConfigHelper.GetConfigDataString(doc, "Case_Type_Hidden_StaticName");
            ContentTypeId = SecureConfigHelper.GetConfigDataString(doc, "ContentTypeId");

}

How to tackle Concurrent Business Process flows

Dynamics 365 has introduced the new feature of Concurrent Business Process Flows. Here is a couple of good articles on that: http://dev...