gotchas

EDIT (06/02/2018): Tanguy reported a scenario where the original code did not work when the user did a “saveandclose” instead of “save”. I have updated the code to handle this scenario. The uses the jQuery library on the parent frame to do the deep clone, but you could very well do the same using lodash clonedeep so that you don’t have to rely on CRM’s jQuery to do the job.

When you want to cancel a save event in CRM Dynamics 365 Customer Engagement, you use “preventDefault()” to block the save operation. This works when you block the operation based on the information that is currently on the form/page, but it does not work, if you want to block the save based on the result of an async operation.

In this contrived example, I would like to block the save of the current form, if there exists an user with “homephone” field set to 12345. The async operation is performed by “retrieveMultipleRecords” which returns a Promise.

Notice the the save event completed and form’s load event fired even though preventDefault ran. The “jobtitle” field that I modified also succeeded, when I expected it to not succeed.

In order to block the save, you’ll have to restructure the code little differently, like the one below. Block save before async operation and explicitly call save, when your criteria for save is met and use closure variable to keep track of whether to save or not.

Business Rules is composed of two components: a client side JavaScript and a server side workflow. You can use these queries to find out the details about this:

LinqPad – Query and result

FetchXml Builder – Query and result

One gotcha with the business rule is that its behavior will deviate, if a field that is required by the business rule is removed on the form. I will demonstrate this, with the business rule below.

This business rule sets the “Contracting Unit” to required, if Order Type is “Work Based”. Below are the screenshots of the form in two different scenarios:

With “Order Type” present on the form

Without “Order Type” present on the form

As you can see, “Contracting Unit” is set to required, only if the “Order Type” field is present on the form, even if the value of “Order Type” is “Work based”. At present, there seems to no check in the form customisation area to prevent a field from being removed, if is required by a business rule. This is how the “Contracting Unit is required” business rule, gets translated into JavaScript.

When you debug this using Chrome Dev Tools, you can easily see why the “Contracting Unit” field is not being set to required.

To assist developers who are troubleshooting why a business rule is not working, I have developed this simple script to run in the DevTools console, that lists the fields that are required by the Business Rules, but are not present in the form. This has to be run in the context of ClientApiWrapper IFrame.

It is saying that “Order Type” should be present in the form, as it is required by a Business Rule that is running on the form. It uses an unsupported internal method to identify this information, and so I recommend that it be used in devtools console only.

I was going through the FetchXML schema and found these two interesting attributes. The first one is “utc-offset” on the “fetch” node.

The other one is “usertimezone” on the “attribute” node.

Both these attributes give an impression, that it is possible to return datetime attributes in a different timezone instead of UTC, but in reality they don’t seem to do anything.

The only way to return datetime in the user’s local timezone, is by using the deprecated ExecuteFetchRequest. I will demonstrate this with XrmToolBox. This is my simple fetch query.

Below is the raw fetch result

Below is the table grid result

I execute the FetchXML using the FetchXML Builder tool. As you can see, the datetime values are different. This is because when you use “RetrieveMultiple” to execute the fetch query, the datetime returned is always UTC and has to be converted to the local timezone. ExecuteFetchRequest is now deprecated and RetrieveMultiple is the recommended way to execute fetchxml. These two points really puzzle me:

Why user-timezone and utc-offset don’t seem to do anything. Are they internally used?

Why MS decided to change the datetime behaviour

So, this is a good point to remember when you are executing fetchxml using “FetchXML Tester” or have set the Result view to Raw fetch result in “FetchXML Builder” or using a mixture of ExecuteFetchRequest and RetrieveMultiple in your code.

You’ll get something like this. Note that the workflow in my case is “Blank Workflow with Custom Step”. Change this to the one you are trying to profile.

Issue 1: Cannot see the workflow step.

If you cannot see the workflow step, that means that there is a mismatch between the Microsoft.Xrm.Sdk.Workflow.dll version and/or your custom workflow step version. Go the folder with the Plugin Registration tool and check the version of the Microsoft.Xrm.Sdk.Workflow.dll version. The important thing is if the major version or minor version of the Microsoft.Xrm.Sdk.Workflow.dll assembly in Plugin Registration tool folder, is different from the one in the Workflow XAML, you will not see the step displayed.

Yesterday, when I was looking into some failed tests, I discovered a quirk of action. If you invoke an action and don’t pass in the arguments that are optional, it doesn’t mean this is going to come up as null in the execution context.

Below is the action I used to replicate the behaviour.

When I execute the action and don’t set any arguments, here is what comes through in the execution context.

Argument Type

Value

Boolean

false

DateTime

0001-01-01T00:00:00

Decimal

0

Entity

null

EntityReference

null

Float

0

Int

0

Money

null

Picklist

null

String

null

EntityCollection

null

The interesting things to note are:

Action arguments, don’t behave the same as AttributeCollection. When you retrieve an entity using the SDK, get the attribute just using the key (not GetAttributeValue), the key won’t exist in the collection, if the attribute value is null. This is not the same behaviour with the action arguments. The key will always be there in the context, even if was not set during the action invocation.

The default value of action arguments = default value of the primitive type

This essentially means that if you are going to do some processing based on the action argument that is a primitive type (Boolean, DateTime, Decimal, Float, Int, String), don’t make it optional. For e.g. if you make a boolean argument optional, how would you differentiate between an action invocation with the argument set to false, and another one which did not set the argument at all?

Generally when you want run a set of tasks that are not realtime, you would do this via Workflow. On the contrary when you want something to happen straightaway, you would do this using a Plugin. Before CRM2015, there was only one type of workflow. Starting with CRM2015 a workflow can be realtime or async. A plugin also can be run asynchronously or synchronously. To summarise quickly:

A realtime workflow is similar to a plugin

An asynchronous plugin is similar to a workflow

Take this particular scenario: You have a plugin that runs synchronously on the Update of a certain entity. Later down the track, you decide you want this plugin to run async, as the set of tasks performed by the plugin doesn’t really have to be realtime and you want to improve the performance of the core operation.

In this scenario, you have to be mindful of one particular plugin behaviour: transaction. When an exception is thrown inside a plugin, the core operation won’t succeed as the exception in the plugin will cause the transaction to rollback. When you change the Execution Mode to “Asynchronous”, you will still see that the plugin has failed in System Jobs area, but the transaction won’t be rolled back.

Assuming that the plugin is registered post-create on a particular entity and Execution Mode is “Synchronous”, there will be no contact called “Max Power” at the end of the plugin execution. But if the same plugin is registered as post-create and asynchronous, there will be a contact called “Max Power” as the exception doesn’t rollback the transaction.

Stages 20 and 40 are guaranteed to be part of the database transaction while stage 10 may be part of the transaction.

But this is not entirely true, as it also depends on the Execution Mode.

tl;dr; Register plugins to run sychronously, if you need the transaction to rollback on plugin exception and need to validate certain criteria for the core operation to succeed. If you don’t need these features, use a workflow.

I recently encountered a weird workflow behaviour that was caused due to my misunderstanding of how DateTime.AddMonths works. The issue happened with a recurring workflow. Since there is no recurring workflow functionality OOB in CRM there are couple of approaches to do this.

I have used a slighly modified version of these approaches to do the recurring workflow functionality. There is only one instance of workflow running every day, and it basically uses fetchxml to grab records that meet a certain criteria and sends out notifications. One of my criteria is “start date 6 months before today” used for 6 month notification. But when you use DateTime.Now.AddMonths(-6) to populate the start date condition, I encountered this behavior.

Here is a quick summary of the issue

Current Date

Code

Expected

Actual

28/08/2015

DateTime.Today.AddMonths(-6).ToString(“s”)

28/02/2015

28/02/2015

29/08/2015

DateTime.Today.AddMonths(-6).ToString(“s”)

01/03/2015

28/02/2015

30/08/2015

DateTime.Today.AddMonths(-6).ToString(“s”)

02/03/2015

28/02/2015

31/08/2015

DateTime.Today.AddMonths(-6).ToString(“s”)

03/03/2015

28/02/2015

So, when the workflow is running on these 4 days from 28/08/2015 to 31/08/2015 and calculating 6 months before the current date, it will produce the same date. As a result when the calculated date is used in a fetch condition as a filter, it will pick the same record(s) on these 4 dates.

The solution to fix your definition of month to 30 days, if that is acceptable. In my case it was OK, as all I did was send out notifications.

tldr; If the resulting day is not a valid day in the resulting month, the last valid day of the resulting month is used