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.

Tuesday, 18 April 2017

How to read configuration data from an xml web resource via JavaScript?

There are situations where you need to keep some environment specific configuration data (e.g. URLs) in a single location without repeating them in all scripts so that all your form scripts 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.)

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 form scripts by calling the following function.

Declare the variables on top and call the function.

var CreateApiUrl;
var UpdateApiUrl;
var ExpireApiUrl;

function GetWebApiConfigurations() {   
    var serverUrl = Xrm.Page.context.getClientUrl();
    var xmlConfigPath = serverUrl + "/WebResources/new_ApiConfiguration.xml";
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open("GET", xmlConfigPath, false);
    xmlHttp.send();
    var doc = xmlHttp.responseXML;
    CreateApiUrl = doc.getElementsByTagName("CreateApiUrl")[0].textContent;
    UpdateApiUrl = doc.getElementsByTagName("UpdateApiUrl")[0].textContent;
    ExpireApiUrl = doc.getElementsByTagName("ExpireApiUrl")[0].textContent;
}

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.

See this post to know how to achieve this via C# code (Plug-ins of custom workflow activity)

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. 

Tuesday, 21 March 2017

How to call a Global action from JavaScript?

We have covered how to call a custom action in JavaScript in a previous blog.
But sometimes there are scenarios you need to create Global Actions where you don't specify an entity in particularly. When you create such a global action and if you need to call that action from your JavaScript code how can you do it?

Let's say this is your Global Action. Please note the name and the Unique Name. Unique name is the one you need to use in the code.



















Then use the following code to call your action. Replace the "your action name" with the unique name of your action. 

function CallAction()
{
    var req = new XMLHttpRequest();
    req.open("POST", Xrm.Page.context.getClientUrl() + "/api/data/v8.0/new_TestGlobalAction", 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) {
                alert("Action Called");
                //Success - No Return Data - Do Something
            } else {
                Xrm.Utility.alertDialog(this.statusText);
            }
        }
    };
    req.send();
}

Make sure your action is activated!

Monday, 20 March 2017

Tracing in Plugins and Custom Workflow Activities

Sometimes when you develop plugins or custom workflow activities in Dynamics CRM you might not get the expected output and you might wonder what went wrong and where. One way to find out this is debug your code. But there is an easier and light weight way of finding out where the error occurred. That is using tracing.


In system settings under customisation you can enable tracing.

















You can enable it for all or only for exceptions. Normally what I do while developing is enable for all. Once you are done with your code you can set it to Exception.
After you set this settings you need to write to the trace in your code. This is how you do it.
First you need to get hold of tracing service instance. After that you can start writing the values of your variables to the trace.

Example code:

using Microsoft.Xrm.Sdk;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Plugin1
{
    public class Class1 : IPlugin
    {
        private const string className = "Class1";
        IOrganizationService service;
        ITracingService tracingService;

        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            service = serviceFactory.CreateOrganizationService(context.InitiatingUserId);
            tracingService.Trace("{0} plugin started", Class1.className);

            var inputs = context.InputParameters;
            foreach (var item in inputs)
            {
                tracingService.Trace("{0} Input paramenter. key: {1}, value: {2}", Class1.className, item.Key, item.Value);
            }
        }
    }
}


Once your plugin or custom workflow activity has executed you can check the plugin trace log in CRM and you will see what values are passed and if an error occurred where it occurred and so on.































Note: There is a small delay in displaying trace logs so be patient J

This feature is handy in production environments too to know what went wrong and where?

Depending on requirement you can enable tracing for all or exceptions only. Normally Exceptions only in Production environments.

To clean up the disc space you can schedule bulk deletion jobs to delete old trace logs (e.g. trace logs which are older than 2 days or so)

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