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

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