Showing posts with label Plugins. Show all posts
Showing posts with label Plugins. Show all posts

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, 21 April 2017

How to read configuration data from an xml web resource via a Plug-in or Custom workflow activity?

URLs) in a single location without repeating them in all Java Scripts or plugins so that all your form scripts and plug-ins can access them.

This way you need to change those variables in only one location when you go from one environment to the other (i.e. Dev to Test and UAT etc.)

In an earlier post I discussed how to access these configurations in Java Scripts. So here I am going to write how to do it in plugins or custom workflow activity.

Let’s say you have created an XML web resource named new_ApiConfiguration.xml.
And assume your web resource looks like this:

<SacConsultingApi>
<CreateApiUrl>https://webapi.sacconsulting.com.au/crmdev/api/record?id=</CreateApiUrl>
<UpdateApiUrl>https://webapi.sacconsulting.com.au/crmdev/api/update?recId=</UpdateApiUrl>
<ExpireApiUrl>https://webapi.sacconsulting.com.au/crmdev/api/expire?recId=</ExpireApiUrl>
</SacConsultingApi>

And you can access these configurations inside you custom plugins or workflow activities by using the below method.

Declare the variables on top.

string CreateApiUrl;
string UpdateApiUrl;
string ExpireApiUrl;


private void ReadWebResourceAndGetData()
{           
   QueryExpression query = new QueryExpression
   {
     EntityName = "webresource",
         ColumnSet = new ColumnSet("name""content"),
         Criteria = new FilterExpression
         {
             Conditions =   {
                       new ConditionExpression
                       {
                            AttributeName = "name",
                            Operator = ConditionOperator.Equal,
                            Values = { "new_ApiConfiguration.xml"}
                       }
                  }
            }
          };    
          EntityCollection ec = service.RetrieveMultiple(query);
          if (ec.Entities[0].Attributes.Count > 0)
          {
              WebResource webResource = null;
              string webResourceContent = string.Empty;
              webResource = (WebResource)ec.Entities[0];
              if (webResource.Attributes.Contains("content"))
              {
                 byte[] binary = Convert.FromBase64String(webResource.Attributes["content"].ToString());
                 webResourceContent = UnicodeEncoding.UTF8.GetString(binary);
              }
              if (!string.IsNullOrEmpty(webResourceContent))
              {
                 XmlDocument xml = new XmlDocument();
                 xml.LoadXml(webResourceContent);
                 XmlNodeList xnList = xml.SelectNodes("CreateApiUrl");
                 if (xnList.Count > 0)
                 {
                     CreateApiUrl = xnList[0].InnerText;                       
                 }
                 xnList = xml.SelectNodes("UpdateApiUrl");
                 if (xnList.Count > 0)
                 {
                     UpdateApiUrl = xnList[0].InnerText;
                 }
                 xnList = xml.SelectNodes("ExpireApiUrl");
                 if (xnList.Count > 0)
                 {
                     ExpireApiUrl = xnList[0].InnerText;
                 }
      }
   }
}

Remember to change the name of the web resource to your web resource name.

This way when you deploy solutions from one environment to the other you don’t need to go and change your configurations in hundred places.

Friday, 24 March 2017

How to update CRM records correctly in C#?

Today I am going to write about a common mistake that CRM developers do very often. If you don’t correct this right now you will face some serious issues with your CRM system and some unpredictable behaviours even in Production systems which will be very hard to troubleshoot.
What is it?
Most of the time you will need to update a CRM record in your plugins or custom workflow activities or some of your console applications which you might write for various requirements.

I will take a plugin for example; Look at this basic plugin which updates the Do Not Email field of a contact record.

public class UpdateContact:IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));           
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.InitiatingUserId);

            using (XrmServiceContext serviceContext = new XrmServiceContext(service))
            {
                if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
                {
                    Entity entity = (Entity)context.InputParameters["Target"];
                    if (entity.LogicalName == Contact.EntityLogicalName)
                    {
                        var contactRecord = serviceContext.ContactSet.FirstOrDefault(c => c.Id == entity.Id);

                        //wrong way! This will update all the fields of the record.
                        contactRecord.DoNotEMail = false;
                        service.Update(contactRecord);


                        //Correct way to update. This will update only the specified field(s)
                        Contact contactRecordToUpdate = new Contact();
                        contactRecordToUpdate.Id = contactRecord.Id;
                        contactRecordToUpdate.DoNotEMail = false;
                        service.Update(contactRecordToUpdate);
                    }
                }
            }
        }
    }

Once you have taken the ID of the entity from the input parameters then you retrieve the Contact record. Now you are setting the field values you need to update on that object. This is the big mistake. If you do so and call update CRM will update all the fields of the entity which will cause to run many other plugins, workflows, business rules, JavaScripts etc.

Correct way is get the ID of the entity from the input parameters; then create a new instance of the entity and set the ID and the field values. Then pass that instance when you call the update method. 

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