Jul 10, 2017

Call WFs and Actions from Plug-ins

When developing server-side custom functionalities for Dynamics 365, our initial though is whether jump in to plug-ins, WFs or Actions. We always need to check the pros and cons of them depending on the scenario.

By the meantime, separating the functionalities among them and using them harmoniously would add more value in terms of flexibility. Below code snippets could be helpful in such an approach.

Call a WF from a Plug-in;

ExecuteWorkflowRequest request = new ExecuteWorkflowRequest()
{
  WorkflowId = new Guid("019813bc-104b-4dc9-93d5-54d93d79908e"), //WF Id
  EntityId = Id
};
ExecuteWorkflowResponse executeWorkflowResponse = (ExecuteWorkflowResponse)service.Execute(request);

Call an Action from a Plug-in;

OrganizationRequest req = new OrganizationRequest("new_profitcalculator");
req["Amount"] = amount; //Parameter
req["Target"] = new EntityReference(new_office.EntityLogicalName, Id);
OrganizationResponse response = service.Execute(req);

May 23, 2017

Web API - Retrieve single record in JavaScript

It is high time to use WEB API for JavaScript calls, though no one is recommending one particular library for that. In fact, underlying argument has two sides;

1) Using a Library - can be tricky since Microsoft can change WEB API and no guarantee new version of Library will be out
2) Without a Library - Coding sequential lines will not manageable in long run

When take both in to account we decided we will have our own light library (easily updated as necessary).

In Order to do this, we found a cool tool that create JS codes for WEB API. This is simply a Dynamics Solution which needs to be deployed first as any other solution. Then you will see the button that launches the tool;


So we generated record Retrieve operation and made below small library for Retrieval of single record in general manner. (We plan to extend this to other CRUD operations).

We made same method to be used to below scenarios;

1) Retrieve record by Id
2) Retrieve record by Criteria

So this is the simple Library. I hope this is manageable than a heavily customized and heavy one.

function RetrieveEntityById(id, entityName, fieldsArry, filterStr) {
    var RetrieveStr = RetrieveStringBuilder(id, entityName, fieldsArry, filterStr);
    return RetrieveEntityByIdWebApi(RetrieveStr);
}

function IdBracketRemover(id) {
    return id.substring(1, id.length - 1);
}

function RetrieveStringBuilder(id, entityName, fieldsArry, filterStr) {
    var Str;

    if (id != null)
    { Str = entityName + '(' + IdBracketRemover(id) + ')' + '?$select='; }
    else
    { Str = entityName + '?$select='; }

    for (i = 0; i < fieldsArry.length; i++)
    { Str = Str + fieldsArry[i] + ','; }
    Str = Str.substring(0, Str.length - 1);
    if (filterStr != null)
        Str = Str + '&$filter=' + filterStr;
    return Str;
}

function RetrieveEntityByIdWebApi(RetriveString) {
    var result;
    var req = new XMLHttpRequest();
    req.open("GET", Xrm.Page.context.getClientUrl() + "/api/data/v8.2/" + RetriveString, 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.setRequestHeader("Prefer", "odata.include-annotations=\"*\"");
    req.onreadystatechange = function () {
        if (this.readyState === 4) {
            req.onreadystatechange = null;
            if (this.status === 200) {
                result = JSON.parse(this.response);
            } else {
                Xrm.Utility.alertDialog(this.statusText);
            }
        }
    };
    req.send();
    return result;
}

Please find the ways of calling the method for two different scenarios as mentioned. Check how parameters become null in different scenarios.

// Query basded on Id
// string : "/customeraddresses(1A170E1D-91AE-4965-8631-0FB9270504D7)"
var fieldsArry = ['city', 'country'];
var Id = '{1A170E1D-91AE-4965-8631-0FB9270504D7}';
var AddressEnt = RetrieveEntityById(Id, 'customeraddresses', fieldsArry, null);
if (AddressEnt != null) {
    alert(AddressEnt["city"]);
}


// Query basded on condition (Parent ID and Prefred = true
// string : "/customeraddresses?$filter=_parentid_value eq 19139FC0-DC44-4E88-A793-924F1F90B08F 
//          and  smsmt_ispreferred eq true"
var fieldsArry = ['city', 'country'];
var ParentId = '{19139FC0-DC44-4E88-A793-924F1F90B08F}';
var filterStr = 'new_ispreferred eq true and  _parentid_value eq ' + IdBracketRemover(Id);
var AddressEnt = RetrieveEntityById(null, 'customeraddresses', fieldsArry, filterStr);
if ((AddressEnt != null) && (AddressEnt.value[0] != null)) {
    alert(AddressEnt.value[0].city);
}

For the moment, this seems to be catering the need. If I develop the other operations, I will post them. Anyway, it is the CRM Rest Builder generated this code for me, what I have done is making little changes with parameters to create correct string to be passed.

Special thanks to my colleague Biplab Singha for guiding to this approach.

Post Note;
Noticed assigning a lookup value after retrieving through this could be little tricky. Please find below code snippet of assigning lookup value (coming from entity called new_shareholders and lookup field called new_shname) to primarycontact;


var shEnt = RetrieveEntityById(null, 'new_shareholders', fieldsArry, filterStr);
if ((shEnt != null) && (shEnt.value[0] != null)) {
    var constLookup = new Array();
    constLookup[0] = new Object();
    constLookup[0].id = shEnt.value[0]['_new_shname_value'];
    constLookup[0].name = shEnt.value[0]['_new_shname_value@OData.Community.Display.V1.FormattedValue'];
    constLookup[0].entityType = shEnt.value[0]['_new_shname_value@Microsoft.Dynamics.CRM.lookuplogicalname'];
    Xrm.Page.getAttribute('primarycontact').setValue(constLookup);
}

Mar 20, 2017

Track Underage contact based on birthday

Dynamics 365 doesn’t give a functionality to identify under age contacts though it captures the birthday by default. Here is a simple code snippet to switch a flag based on age. We can now simply distinguish under age (<18) contact easily through a view.

SetUnderAgeFlag = function() {
  var birthDate = Xrm.Page.getAttribute('birthdate').getValue();
  if (birthDate  != null)
  {
      var today = new Date();
      var nowyear = today.getFullYear();
      var nowmonth = today.getMonth();
      var nowday = today.getDate();             

      var birthyear = birthDate.getFullYear();
      var birthmonth = birthDate.getMonth();
      var birthday = birthDate.getDate();

      var age = nowyear - birthyear;
      var age_month = nowmonth - birthmonth;
      var age_day = nowday - birthday;

      if ( (age < 18) || ((age==18) && (age_month<0)) || ((age==18) && (age_month==0) && (age_day <0)) )
      {         
         Xrm.Page.getAttribute('new_under18flag').setValue(true);
      }
      else
      {
         Xrm.Page.getAttribute('new_under18flag').setValue(false);
      } 
      Xrm.Page.getAttribute('new_under18flag').setSubmitMode('always');  
  }   
}

Jan 4, 2017

Calling third party Web service from Dynamics CRM online plug-in

Just thought of sharing this important code snippet. Please have a closer look at Binding Configuration part which is the essence of the exercise.

public class PostUpdateTransaction : IPlugin
{
  public void Execute(IServiceProvider serviceProvider)
  {
    IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
    IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
    IOrganizationService service = (IOrganizationService)serviceFactory.CreateOrganizationService(context.UserId);
    ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

    if (context.Depth > 1)
    {
        return;
    }

    try
    {
        BasicHttpBinding myBinding = new BasicHttpBinding();
        myBinding.Name = "BasicHttpBinding_Service";
        myBinding.Security.Mode = BasicHttpSecurityMode.Transport;
        myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
        myBinding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
        myBinding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;

        EndpointAddress endPointAddress = new EndpointAddress(@"https://XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.asmx");
        ClassLibrary1.XXXXX.XXXClient serviceClient = new ClassLibrary1.XXXXX.XXXClient(myBinding, endPointAddress);
        string xmlRequest = @"XXX";

        string result = serviceClient.<Method>(xmlRequest);
        XmlDocument resultXML = new XmlDocument();
        resultXML.LoadXml(result);

     }
     catch (Exception ex)
     {
        throw new InvalidPluginExecutionException(ex.Message);
     }
     finally
     {

     }
  }
}