Sep 17, 2017

Importing Duplicate Detection Rules

Since early versions of Dynamics 365, we have been using Duplicate Detection rules and it is a handy feature. Ironically, still we can't import those rules with Solution. In most cases, users configure them in different environment and its not a big deal. Still, it can be error-prone and inconsistent.

So alternative is to consider Duplicate Detection Rule entities as reference data and import them using Configuration Migration Tool coming with SDK. Usually you will find this tool in below location of the SDK;

\SDK\Tools\ConfigurationMigration

Anyway, below are the two points to keep in mind when importing Duplicate Detection rules;

1) Rules should be Unpublished before importing.
2) Need to include both Duplicate Detection Rule and Duplicate Rule Condition in correct Order.


Hope this helps.

Sep 14, 2017

Convert in between Local Time and UTC Time

Though this is not a new subject, thought of sharing a simplified version of Code snippet to be used in converting Local Time and UTC Time as necessary. Base for this code was taken from SDK, but I have simplified and made a Ready Made class, which I though is helpful.

Let's remind few reasons why these conversions are very important to Dynmics 365 implementations;
  • DateTime fields are always kept in DB as UTC, but shown in UI with Users Local Time.
  • DateTime Retrieve with Web Service is always UTC.
  • Plugin/WF Context returns UTC time. 
Below are some of the scenarios you may come across that need conversions in your code.
  • When you need to do a calculation based on Time
  • When you need to pass Data to another System

Here is the class I suggest;
using Microsoft.Xrm.Sdk;
using System;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;

namespace CommonLibrary
{
    internal static class CommonUtility
    {
        internal static DateTime RetrieveLocalTimeFromUTCTime(IOrganizationService service, DateTime utcTime)
        {
            return RetrieveLocalTimeFromUTCTime(utcTime, RetrieveCurrentUsersSettings(service), service);
        }

        internal static DateTime RetrieveUTCTimeFromLocalTime(IOrganizationService service, DateTime localTime)
        {
            return RetrieveUTCTimeFromLocalTime(localTime, RetrieveCurrentUsersSettings(service), service);
        }

        internal static int? RetrieveCurrentUsersSettings(IOrganizationService service)
        {
            var currentUserSettings = service.RetrieveMultiple(
                new QueryExpression("usersettings")
                {
                    ColumnSet = new ColumnSet("timezonecode"),
                    Criteria = new FilterExpression
                    {
                        Conditions =
                        {
                    new ConditionExpression("systemuserid", ConditionOperator.EqualUserId)
                        }
                    }
                }).Entities[0].ToEntity<Entity>();
            return (int?)currentUserSettings.Attributes["timezonecode"];
        }

        internal static DateTime RetrieveLocalTimeFromUTCTime(DateTime utcTime, int? timeZoneCode, IOrganizationService service)
        {
            if (!timeZoneCode.HasValue)
                return DateTime.Now;
            var request = new LocalTimeFromUtcTimeRequest
            {
                TimeZoneCode = timeZoneCode.Value,
                UtcTime = utcTime.ToUniversalTime()
            };
            var response = (LocalTimeFromUtcTimeResponse)service.Execute(request);
            return response.LocalTime;
        }

        internal static DateTime RetrieveUTCTimeFromLocalTime(DateTime localTime, int? timeZoneCode, IOrganizationService service)
        {
            if (!timeZoneCode.HasValue)
                return DateTime.Now;
            var request = new UtcTimeFromLocalTimeRequest
            {
                TimeZoneCode = timeZoneCode.Value,
                LocalTime = localTime
            };
            var response = (UtcTimeFromLocalTimeResponse)service.Execute(request);
            return response.UtcTime;
        }
    }
}

Suppose we need to Retrieve Local time from UTC time (dateTime could be a field coming from Plug-in context);

DateTime dateTimeLocal = CommonUtility.RetrieveLocalTimeFromUTCTime(service, dateTime);

Sep 11, 2017

Azure Subscriptions for Dynamics 365 Developers

Now it’s time to jump in to Azure and find out what’s available for Dynamics Developers. First thing first, how to get azure subscription. I mean FREE subscription to play around and see.

Option 1
Get the general Azure Trial worth $200.

Check: https://azure.microsoft.com/en-au/free/

Only issue with this option is it last only 30 days, you must complete all your drama in 30 days!

Option 2
Register for Dev essentials program, which gives $300 worth credit for one year, which is good.

Check: https://www.visualstudio.com/dev-essentials/


Warning
What so ever, one thing you may need to know is these free subscriptions are NOT per account, BUT per person. Though you try with different email addresses Microsoft will nail you down with Mobile OR/AND Credit Card details. In fact, you can use one of the above two choices.
Sadly, registration form just through a generic error (i.e. We cannot proceed with signup due to an issue with your account. Please contact billing support) and you may not be able to understand why you can’t register.


Option 3
Now come the thrilling part. As Dynamics Developers we always work with Dynamics 365 trials. Though they expire in 30 days, we are ALLOWED to create as many instances as we want.

Check: https://www.microsoft.com/en-us/dynamics/free-crm-trial.aspx

Actually, once you create Dyanmics 365 instance, relevant Admin user get free subscription automatically assigned to that account! Isn’t it fun?

Problem solved! Though it is painful to create Dynamics 365 trials again and again, at least we know that we can get Azure subscriptions as we wish regardless of how many we had in the past.

Feel privileged to be a Dynamics 365 Developer!

(Thanks Sam and Proveen for the help in this regards)

Aug 29, 2017

Retrieve Multiple - FetchXML with Alias

This is not a complex thing, but though of posting the code snippet since I found it important to explain a bit. When we need to retrieve data in related records few levels away from our current context, best method is using the FetchXML and use ReiveMultiple method. This will avoid us using many server calls to reach our entity.

Reason for writing this post is to explain one point. When we do complex FetchXMLs we definitely need to use Alias for linked entities. So reading the data in the code can be little different.

Check my example; I have an entity called job. Job got a lookup field to Project. Project got a lookup to User. Suppose we need to read the Mobile Number of the user (3 levels ahead) while we have only Job Id in hand, we use FetchXML as below;

internal static EntityCollection RetriveProjectAdminMobileByJobId(IOrganizationService service, Guid Id)
{
var fetchXml = string.Format(@"<fetch mapping='logical' output-format='xml-platform' version='1.0' distinct='false'>  
               <entity name='new_job'>
               <filter type='and'>
               <condition attribute='new_jobid' operator='eq' value='{0}' />
               </filter>
                   <link-entity name='new_project' to='new_jobprojectid' from='new_projectid' alias='PROJ' link-type='outer' visible='false'>
                       <link-entity name='systemuser' to='new_projectadmin' from='systemuserid' link-type='outer' alias ='PROJADMIN'>
                            <attribute name='mobilephone' />
                        </link-entity>
                   </link-entity>
               </entity>
               </fetch>", Id);
            return service.RetrieveMultiple(new FetchExpression(fetchXml));

Please notice, how I have used Aliases meaningfully. Now what we need to keep in mind is resulting fields would come with that Alias. In fact, mobile phone field is like PROJADMIN.mobilephone. Now check how I read it in C#;

var entityCollection = RetriveProjectAdminMobileByJobId(service, workOrderRef.Id);

if (entityCollection == null || entityCollection.Entities == null || entityCollection.Entities.Count <= 0)
    return;

if (entityCollection.Entities[0].Attributes.Contains("PROJADMIN.mobilephone"))
    string AdminMobile = (string)((AliasedValue)entityCollection.Entities[0].Attributes["PROJADMIN.mobilephone"]).Value;

I advice always try the FetchXmls before using. Best tool to do so is FetchXM Tester of XrmToolBox.


There is a one pitfall. I have seen some tools which are not returning the correct field names as expected. For example, I have tested same FetxhXML in DataSet creating tool in SSRS report authering extensions in VS2012 that returned like PROJADMIN-mobilephone, which is WRONG.

Aug 16, 2017

Code snippet to add Lookup filter

This is just a simple JavaScript to filter lookup based on existing field value in the form.

In this scenario, we will assume we needs to set Payment Method to a lookup field (i.e. new_paymentmethodid). Its user-friendly if we can filter the lookup values to show only the payments of contact (i.e. new_contactid). This code snippet will work for this.

PaymentMethodFilter = function () {
    Xrm.Page.getControl("new_paymentmethodid").addPreSearch(addPaymentMethodFilter);
}

addPaymentMethodFilter = function () {
    var contact = Xrm.Page.getAttribute('new_contactid');
    if ((contact == null) || (contact.getValue() == null) || (contact.getValue()[0] == undefined)) return;
    var paymentMethodFilter = "<filter type='and'><condition attribute='new_paymentowner' uiname='" + contact.getValue()[0].name + "' operator='eq' uitype='contact' value='" + contact.getValue()[0].id + "' /></filter>";
    Xrm.Page.getControl("new_paymentmethodid").addCustomFilter(paymentMethodFilter, "new_paymentmethod");
}

Now call PaymentMethodFilter method for onChange of new_paymentmethodid field.

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
     {

     }
  }
}