Dynamics 365 v9.0: String (RESX) Web Resources – A Better World with Localization

Hello everyone,

In today’s post I would like to discuss about the feature introduced in Dynamics 365 v9.0 with which complete localization can be achieved. If we take a look back at prior versions of CRM, localization can be achieved for Entities and it’s sub components using “CrmTranslations.xml” file in the solution. However, there is no OOB support for achieving the same for custom web resources like JScript and HTML. With introduction of “String (RESX) Web Resources”, we have got complete package of localization solution.

Before jumping into more details, we need to understand another feature introduced in Dynamics 365 v9.0 which is required in order to make String Web Resources work. In my prior post, i touched up on using Attribute dependencies for JScript web resources. Similarly, we also got Web Resource Dependencies introduced. Let’s quickly understand this feature. For an instance, we have JScript web resource named “new_servicehelpers.js” which uses JQuery and JSON libraries. In prior versions of CRM, in order to use “new_servicehelper.js” on a form we require to add JQuery and JSON JScript web resources as well on the form libraries. This is one of the tough task developer has to undergo in order to keep track of dependent web resources and their order of loading. With v9.0 on wards, we can simply add JQuery and JSON web resources as dependencies for the “new_servicehelper.js” web resource by adding them to “Dependencies” tab’s first grid. By doing this, CRM will automatically download JQuery and JSON web resources wherever “new_servicehelper.js” is about to load on a form. In one of my previous implementations, we used require.js library to achieve modular and dependency file loading. Now, we got it this OOB. Later in this post, i will show how we can use the same concept for String Web Resources.

So, lets start looking into what is a String Web Resource, how to create and use it in the other web resources. For this post, I will be focusing on using String Web Resources in JScript Web Resources.

At a high level, following are the quick bits about working with String Web Resources:

  1. String Web Resource’s file extension is .resx and uses RESX XML which holds keys and localized strings. This is the standard format used across windows applications.
  2. String Web Resources has to follow a standard naming convention. It’s format is webresourcename.languagecode.resx. ex: new_text.1033.resx
  3. For same content, separate String Web Resources has to be created for each language.
  4. Add String Web Resources as Web Resource Dependencies to JScript Web Resources.
  5. Use Xrm.Utility.getResourceString(webResourceName, key) to get the localized string based on the current logged in user’s language code.

Creating .RESX file:

As i mentioned earlier, RESX XML is the standard format used across windows applications. So, lets go to Visual Studio and find out how to create it in a simple way.

  1. Add a Resource File as shown below to one of your existing or a new project. We don’t need to follow CRM standard naming convention for String Web Resources here for this .resx file in Visual Studio but I suggest we follow that here as well.
    My bad: I could have named this file as “new_Messages.1081.resx”

VS_AddItem_RESX

2. Open Designer view of newly created Resource File and add keys and respective localized strings. Following is the Resource File i created for “Hindi” language. Comments are optional.

VS_Hindi.png

Following is the auto generated RESX XML code generated for this file. No need to check what has been written in this XML file. Save your time for other important things.!!

<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema

Version 2.0

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>

There are any number of "resheader" rows that contain simple
name/value pairs.

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ApprovalConfirmRequest" xml:space="preserve">
<value>Do you really want to approve this request?</value>
<comment>Approval Confirmation Dialog </comment>
</data>
<data name="ApprovalReject" xml:space="preserve">
<value>Request is Rejected</value>
<comment>Approval Rejection Message</comment>
</data>
<data name="ApprovalSuccess" xml:space="preserve">
<value>Request Approved Successfully</value>
<comment>Approval Success Message</comment>
</data>
<data name="CancelButtonLabel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="ConfirmButtonLabel" xml:space="preserve">
<value>Approve</value>
</data>
<data name="GotIt" xml:space="preserve">
<value>Got it</value>
</data>
<data name="SubTitle" xml:space="preserve">
<value>This action will approve the request</value>
<comment>This action will approve the request</comment>
</data>
<data name="Title" xml:space="preserve">
<value>Approval Request</value>
<comment>Approval Request</comment>
</data>
</root>

3. Create a new String Web Resource in CRM. Here are the important things to consider.

  • Use Standard naming convention. For this web resource, i named it as “new_Messages.1081.resx”. 1081 is the language code for Hindi. For a list of language codes, please check this link
  • Select Type as “String (RESX)
  • Select appropriate language in the Language dropdown. For this web resource, i have selected “Hindi (India).
  • Choose .resx file created in Visual Studio. As of we don’t have an option to view/update RESX web resource content within CRM. So, any changes has to be done has to happen in Visual Studio for now. So, better if we follow the CRM naming convention in Visual Studio project as well.

WebResource_Hindi

4. Similarly, I am creating one more RESX web resource for English with same keys but with english strings.

Name of the web resource: new_Messages.1033.resx.

We require to keep the name similar except the language code which is 1033. The reason being, we would like CRM to auto load either of these files based on user’s language code.

VS_English

WebResource_English

5. Now, lets add these newly created two String Web Resources as dependencies to JScript Web Resource. In one of my previous post, i have used JScript to show Confirm and Alert dialogs using Xrm.Navigation methods. Check out the post here . I will use the same JScript web resource and update it to use String Web Resources to show localized content.

Here is the updated JScript code to use:

function GetResourceString(webResourceName, key) {
return Xrm.Utility.getResourceString(webResourceName, key);
}

function ApproveRequest() {
var reqTitle = GetResourceString("new_Messages", "Title");
var reqText = GetResourceString("new_Messages", "ApprovalConfirmRequest");
var reqSubtitle = GetResourceString("new_Messages", "SubTitle");
var reqConfirmButtonLabel = GetResourceString("new_Messages", "ConfirmButtonLabel");
var reqCancelButtonLabel = GetResourceString("new_Messages", "CancelButtonLabel");
var confirmStrings = { title: reqTitle, text: reqText, subtitle: reqSubtitle, confirmButtonLabel: reqConfirmButtonLabel, cancelButtonLabel: reqCancelButtonLabel };
var confirmOptions = { height: 200, width: 450 };
//New v9.0 Method
Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions).then(successCallback, errorCallback);
}

function successCallback(success) {
var approvedText = GetResourceString("new_Messages", "ApprovalSuccess");
var rejectedText = GetResourceString("new_Messages", "ApprovalReject");
var okText = GetResourceString("new_Messages", "GotIt");
//On Click on Approve Button
if (success.confirmed) {
//Here Logic to Approve the Request
//
var alertStrings = { confirmButtonLabel: okText, text: approvedText };
var alertOptions = { height: 120, width: 260 };
//New v9.0 Method
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
}
else {
var alertStrings = { confirmButtonLabel: okText, text: rejectedText };
var alertOptions = { height: 120, width: 260 };
//New v9.0 Method
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
}
}

function errorCallback(fail) {
//On Error Logic
}

Important changes done in this JScript web resource:

  1. Used Xrm.Utility.getResourceString(webResourceName, key) method to get the localized string.
  2. Used first part of the web resource name new_Messages and excluded remaining .languaguecode.resx part of it as the first parameter to the method. Reason for this obvious. We expect CRM product to auto select appropriate RESX file based on the user’s language. So, as a developer we need to pass the first part of the web resource name which is common for all language files and let CRM fills the language code and choose the appropriate one.

6. As shown below, under Dependencies tab for the JScript web resource add the RESX web resources in the first grid. Care to know about what’s in the second grid? Check out my previous post here

Add_RESX_Dependencies

That’s it. We are done. Following is the result:

In Hindi(Indian)

ConfirmDialog_Hindi

AlertDialog_Hindi

In English:

ConfirmDialog_English

AlertDialog_English

Few more things to know:

  1. Organization’s default language is the fallback language option for RESX web resources. Let’s say my organization’s default language is English and I enabled Hindi and French as other available languages. With our current customizations in this post when a user with French language logs in, English RESX file will be loaded for them.
  2. For a No Key Found Scenarios where a key is not available in the loaded RESX file, a null value will be returned.

 

Hope it helps..!!!

Dynamics 365 v9.0: No More Hidden Fields

Hello everyone,

As a developer, we come across many situations where we keep a hidden field on the form only for the purpose of data manipulations. This approach always worked. However, it was difficult to keep track of what hidden fields are required on the form and by which java script web resource.

This poses two issues:

1. Sometimes we may end up accidentally removing hidden fields from forms

2. It will be a time consuming task to come up with the list of hidden fields to add on a form if we plan to reuse a web resource which uses hidden fields.

With Dynamics 365 9.0, we can avoid these situations with “Attribute Dependencies for JavaScript Web Resources”. Here is what we have to do to accomplish this. Under “Dependencies” tab of the web resource(only JScript), add attributes(so called hidden fields) to the second grid. All the attributes added in this grid are no longer required to be present on the form. When the web resource loaded on the entity form, it makes sure the dependent attributes are automatically added to the attribute collection on to the form. This allows the JScript web resource to do data manipulations without adding hidden fields to the form. One thing to note here is, these dependent attributes are only added to the attributes collection and not to the controls collection which means we cannot use “data.ui” related methods.

EntityAttList

AttList

Observations:

  1. Attribute Dependencies are only available for JScript web resources.
  2. Dependent attributes are added only to the form attribute collection. So, only attribute methods can be used.
  3. Attributes can be added from multiple entities. This is useful for the web resources reused across multiple entities.
  4. Once dependent attributes are added to attributes collection on the form, any web resource(JScript and HTML) on the form and Business Rules can use those fields for data manipulations.

Hope it helps..!!

Dynamics 365 v9.0 – Xrm.Navigation namespace methods – Part 1

Hello everyone,

Dynamics 365 v9.0 has introduced Xrm.Navigation namespace under Xrm object. It has a mix of new methods and old methods moved from Xrm.Utility.

Following is the list:

Dynamics 365 v9.0 Comments
openAlertDialog  Deprecated Method: Xrm.Utility.alertDialog
openConfirmDialog  Deprecated Method: Xrm.Utility.confirmDialog
openErrorDialog  Introduced in v9.0
openFile  Introduced in v9.0
openForm   Deprecated Methods:
Xrm.Utility.openEntityForm
Xrm.Utility.openQuickCreate
openUrl    Introduced in v9.0
openWebResource   Deprecated Method:

In this blog post, I would like to discuss about the new features added in openAlertDialog and openConfirmDialog methods.

Prior to v9.0:

Syntax:

Xrm.Utility.confirmDialog(message,yesCloseCallback,noCloseCallback)
Xrm.Utility.alertDialog(message,onCloseCallback)

Sample Code:


Xrm.Utility.confirmDialog("Are you sure you want to approve this request?",
//Confirm Dialog Success Callback
function () {
Xrm.Utility.alertDialog("Request Approved Successfully",
//Alert Dialog Ok Button Callback
function () {
//
});
},
//Confirm Dialog Close Callback
function () {
Xrm.Utility.alertDialog("Request Aborted",
//Alert Dialog Ok Button Callback
function () {
//
});
});

Result:

v9.0:

Syntax:

Xrm.Navigation.openConfirmDialog(confirmStrings,confirmOptions).then(successCallback,errorCallback);
Xrm.Navigation.openAlertDialog(alertStrings,alertOptions).then(closeCallback,errorCallback);

Following is the rewritten code for v9.0 for showing confirm and alert dialogs using openConfirmDialog and openAlertDialog methods:

function ApproveRequest() {
var confirmStrings = { title: "Approval Request", text: "Are you sure you want to approve this request?", subtitle: "This action will approve the request", confirmButtonLabel: "Approve", cancelButtonLabel: "Cancel" };
var confirmOptions = { height: 200, width: 450 };
//New v9.0 Method
Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions).then(successCallback, errorCallback);
}

function successCallback(success) {
//On Click on Approve Button
if (success.confirmed) {
//Here Logic to Approve the Request
//
var alertStrings = { confirmButtonLabel: "Got it..!!", text: "Request Approved Successfully" };
var alertOptions = { height: 120, width: 260 };
//New v9.0 Method
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
}
else {
var alertStrings = { confirmButtonLabel: "Got it..!!", text: "Request Cancelled" };
var alertOptions = { height: 120, width: 260 };
//New v9.0 Method
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
}
}

function errorCallback(fail) {
//On Error Logic
}

Result:

ConfirmDialog_V9_1.png

AlertDialog_V9_1

openConfirmDialog:

Here are the improvements added in V 9.0:

  1. Better UI
  2. Ability to specify labels for confirm and cancel buttons
  3. In addition to text in the dialog title and subtitle can be added
  4. Add dialog window height and width in pixels

ConfirmDialog_V9

Note: successCallback gets called for both Confirm and Cancel Buttons. Esc and x button click as well mimics Cancel Button click. Here is how we can differentiate Confirm and Cancel button the events:

function successCallback(success) {
//success.confirmed = true for Confirm Button Click
if (success.confirmed) {
//Confirm Button Click Event Handler
}
else {
//Cancel Button Click Event Handler
}

openAlertDialog:

Here are the improvements added in V 9.0:

  1. Better UI
  2. Ability to specify labels for confirm button
  3. Add dialog window height and width in pixels

When you are upgrading to Dynamics 365 v9.0, plan to rewrite code to include these new methods.

Hope it helps..!!

Dynamics 365 v9.0 – Set Multi-Select Option Set values using JScript

Hello everyone,

In my previous post on Multi-Select Option Sets , I mentioned about setting multi-select option set values using setValue() method overwrites existing selected values. In this post I would like to discuss more on that and how we can append or remove certain values using simple JScript methods.

getValue() / getText()- Returns Array

function SetMultiSelValues(executionContext) {
//Get Form Context from Execution Context
var formContext = executionContext.getFormContext();

//Get Array of Selected OptionSet Values
//Returns: [100000005, 100000001]
var selectedValues = formContext.getAttribute("new_multiselect").getValue();

//Get Array of Selected OptionSet Text
//Returns: ["Six", "Two"]
var selectedOptionText = formContext.getAttribute("new_multiselect").getText();
}

setValue() – To Overwrite existing Values

Args: Pass integer or Array of integers

function SetMultiSelValues(executionContext) {
//Set Value a Single Value - Overwrites Existing Selected Values
formContext.getAttribute("new_multiselect").setValue(100000005);

//or

formContext.getAttribute("new_multiselect").setValue([100000005]);

//Set Multiple Values - Overwrites Existing Selected Values
formContext.getAttribute("new_multiselect").setValue([100000003, 100000004]);
}

setValue() – To Append to existing Values

Here I am using concat() JScript method to concatenate existing and new values

function SetMultiSelValues(executionContext) {
//Get Form Context from Execution Context
var formContext = executionContext.getFormContext();

//Get Array of Selected OptionSet Values
//Returns: [100000005, 100000001]
var existingValues = formContext.getAttribute("new_multiselect").getValue();

//Append a set of values
var newValues = [100000003, 100000004];
var updatedValues = ConcatArrays(existingValues, newValues);

//Appends to Existing Selected Values
//New Values: [100000005, 100000001, 100000003, 100000004]
formContext.getAttribute("new_multiselect").setValue(updatedValues);
}
function ConcatArrays(existingValues, newValues) {
if (existingValues === null || Array.isArray(existingValues) === false) {
return newValues;
}

if (newValues === null || Array.isArray(newValues) === false) {
return existingValues;
}
return existingValues.concat(newValues);
}

setValue() – To Remove List of Values

Here I am using JScript’s filter() method to remove values from selected option set values array


function SetMultiSelValues(executionContext) {
//Get Form Context from Execution Context
var formContext = executionContext.getFormContext();

//Get Array of Selected OptionSet Values
//Returns: [100000005, 100000001, 100000003, 100000004]
var existingValues = formContext.getAttribute("new_multiselect").getValue();

//Removes from Existing Selected Values
//New Values: [100000005, 100000004]
var removeValues = [100000001, 100000003];
updatedValues = RemoveFromArray(existingValues, removeValues);
formContext.getAttribute("new_multiselect").setValue(updatedValues);
}

function RemoveFromArray(existingValues, removeValues) {
if (existingValues === null || Array.isArray(existingValues) === false) {
return removeValues;
}

if (removeValues === null || Array.isArray(removeValues) === false) {
return existingValues;
}

return existingValues.filter(function (value, index) {
return removeValues.indexOf(value) == -1;
})
}

Hope it helps..!!

Dynamics 365 v9.0 – Show Lookup Dialog is here

Hello everyone,

Microsoft has recently released Developer Guide for Dynamics 365 v9.0. From now on wards, only online version of documentation is available. You can find latest documentation here

One of the new Dev feature caught my eye was the ability to show Lookup Dialog using Xrm.Utility.lookupObjects method. Earlier, we used to go with unsupported method to show Lookup Dialog which is no longer required. Here is how it has to be done in v9.0 onwards.

Lets assume a scenario where we have a custom ribbon button “Assign”.

lookupobjects_assign

when user clicks on this ribbon button a Lookup Dialog has to be shown with list of system users. On selection of a specific user, owner field has to be updated with it and record has to saved.

lookupobjects_lookupdialog.png

Following is the code which does the required functionality and also

  • Limits list of views to be shown
  • Sets Return Value to Single

This new method expects an object with list of parameters to be passed and success and Cancel callback functions.

function SelectNewAssignee() {

var lookupOptions = {};
//list of entities to be displayed
lookupOptions.entityTypes = ["systemuser"];
//entity to be shown bydefault in lookup dialog
lookupOptions.defaultEntityType = "systemuser";
//default view
lookupOptions.defaultViewId = "{00000000-0000-0000-00AA-000010001019}";
//Allow Multiple Records to be selected or not
lookupOptions.allowMultiSelect = false;
//List of views to be available
lookupOptions.viewIds = ["{00000000-0000-0000-00AA-000010001019}", "{00000000-0000-0000-00AA-000010001020}"];
//This is for Mobile Devices
//lookupOptions.showBarcodeScanner = false;

Xrm.Utility.lookupObjects(lookupOptions).then(AssigneeSuccessCallback, AssigneeCancelCallback)
}

//Success Callback for Lookup Dialog
//Gets Array of selected records in the lookup dialog
function AssigneeSuccessCallback(selectedItems) {
if (selectedItems != null &amp;&amp; selectedItems.length &gt; 0) {
var newAssignee = [{ id: selectedItems[0].id, typename: selectedItems[0].typename, name: selectedItems[0].name }];
Xrm.Page.getAttribute("ownerid").setValue(newAssignee);
Xrm.Page.data.entity.save();
}
}

function AssigneeCancelCallback() {
}

Other capabilities of this method:

  1. Ability to show specific entities
  2. Ability to show specific views
  3. Ability to make single or multi selection of the records
  4. Show Bar Code for Lookup Control on Mobile Devices(I haven’t tried this yet)

 

Hope it helps..!!

How – To Series 21: USD – Replacement Keys “A Must Use for USD Development”

Hello everyone,

In this blog post, i would like to discuss about the importance of “Replacement Keys” in USD. This is especially important for the beginners in USD development.

To keep it simple, “As a good practise when using Replacement Parameters in USD, make sure to use appropriate Replacement Keys”. Ok, sound good. But, what if we don’t use them? To answer it, first we need to understand how the code written in RunScript and RunXrmCommand(and wherever Replacement Parameters are used) gets Exected.

Lets assume we have an action call to set some values on case form for 3 fields. Out of these 3, we have one field “new_incomingphonenumber” whose value is set from the context’s “PhoneNumber” Replacement Parameter.

Following is the action call’s script:

Xrm.Page.getAttribute(“description”).setValue(“[[$Context.CallNotes]]”);
Xrm.Page.getAttribute(“new_incomingphonenumber”).setValue(“[[$Context.PhoneNumber]]”);
Xrm.Page.getAttribute(“new_sessionid”).setValue(“[[$Session.ActiveSession]g]”);

 

When case form loads and all keys exists in the $Context and $Session, all 3 fields gets it’s data populated.

When we look into “Debugger”, this  action call’s “Action Data” following will be the script that was executed.

Xrm.Page.getAttribute(“description”).setValue( “Compalint Regarding ABC Product”);
Xrm.Page.getAttribute(“new_incomingphonenumber”).setValue( “123-456-7890”);
Xrm.Page.getAttribute(“new_sessionid”).setValue( “8e3cdc83-2dd7-462a-b428-6b6a831b086e”);

One main thing we have to observe here is, all replacement parameters are replaced with their corresponding values.

Let’s assume we don’t have “PhoneNumber” key in the $Context.

In this case, we get the following error in the debugger when the action call runs:

Action Call# 1

Source:Event(Ticket:BrowserDocumentComplete)
Name:Populate Ticket Default Values
Application:Ticket
Action:RunXrmCommand
Action Data:Xrm.Page.getAttribute(“description”).setValue(“Compalint Regarding ABC Product”);
Xrm.Page.getAttribute(“new_incomingphonenumber”).setValue(“[[$Context.PhoneNumber]]”); 
Xrm.Page.getAttribute(“new_sessionid”).setValue(“7103ada9-c2ba-4fe7-b246-82fc5864407e”);
Parameters:”frame”=””
“url”=”http://crmserver/FunStuff/main.aspx?etn=incident&id=&extraqs=&pagetype=entityrecord#618247925”

Condition:
Condition Result:ActionFailed
Exception Details:Not all parameters in the action call RunXrmCommand are available, stopping the action call. The parameters are : Xrm.Page.getAttribute(“description”).setValue(“Compalint Regarding ABC Product”);
Xrm.Page.getAttribute(“new_incomingphonenumber”).setValue(“[[$Context.PhoneNumber]]”); 
Xrm.Page.getAttribute(“new_sessionid”).setValue(“7103ada9-c2ba-4fe7-b246-82fc5864407e”);
======================================================================

 

Due to this error, none of the field values are populated. Following will be the “Action Data” for this action call.

 

If we check the “Action Data”, “Phone Number” key is not replaced.

Xrm.Page.getAttribute(“new_incomingphonenumber”).setValue(“[[$Context.PhoneNumber]]”); 

Even though one replacement parameter is not replaced, we expects to see at least first field gets their data populated and remaining fields are blank. However, this is the difference: “In USD, scripts are first get parsed as regular text with all of the Replacement Parameters before they get executed”.  In our case, USD tried to replace “[[$Context.PhoneNumber]]” with it’s value from $Context and it couldn’t find the “PhoneNumber” key. When Action Call runs, it throws an error due to this.

To solve this, we have to use appropriate “Replacement Keys” when using “Replacement Parameters”. In our case, it will be a “+” sign like “[[$Context.PhoneNumber]+]”

Xrm.Page.getAttribute(“description”).setValue(“[[$Context.CallNotes]+]”);
Xrm.Page.getAttribute(“new_incomingphonenumber”).setValue(“[[$Context.PhoneNumber]+]”);
Xrm.Page.getAttribute(“new_sessionid”).setValue(“[[$Session.ActiveSession]g+]”);

So, is it the “+” only character which we need to use to avoid these kind of issues? Answer is No. “+” is useful to avoid non-existent key issues and to replace it with empty strings. As I mentioned just before, USD parses your entire script before it gets executed. So, what if one of your replacement parameter has URL or quote or line breaks in it’s value? When these values exist, action call fails executing the script as we haven’t escaped these characters and script becomes invalid.

Check out the following MSDN article which has the list of Replacement Keys and their purpose.

https://msdn.microsoft.com/en-us/library/dn864934.aspx#ReplacementKeys

One more thing, we can use combination of these replacement keys to benefit from multiple scenarios. If we have a look at the following line of code which we used to set Session Id, we used “g+” replacement key. “g” being used to get Replacement Parameter from Global Session and “+” replacement key replaces it with empty string when that replacement parameter doesn’t exist.

Xrm.Page.getAttribute(“new_sessionid”).setValue(“[[$Session.ActiveSession]g+]”);

This is the post which mainly targeted for beginners in USD development and I hope it helps.!!

Cheers

Vikranth

How – To Series 20: MultiSelect Option Set in Dynamics 365

Hello everyone,

Today i want to do a quick post about “MultiSelect Option Set”, one of the most awaited feature in Dynamics 365. It used be a frequent business need and we end up developing HTML web resource and N to N relationship with custom entity to provide required functionality.

I like the new control and its easy of use. There are quite a good number of blog posts talking about this new feature. However, I wanted to mention some of my observations about this new control.

  1. MultiSelect Option Set allows maximum of 150 values to be selected. If we try to select more than 150 and try to save the form we will get following error.

 

 

 

 

 

 

 

 

 

2. We cannot set default vlaue(s) during the field customization.

3. setValue() method expects an array as it’s parameter and overwrites existing selected options. I know it’s too early to make a comment until SDK for v9.0 is released as we may expects to see some new methods specifically for MutliSelect Option Set control.

Thanks

How – To Series 19: USD Error – Failed to Create Hosted Control

Hello everyone,

This blog post is on issue related to Unified Service Desk. We were getting an error whenever we create a Hosted Control in any of the organization in our Dev server. This started happening all of a sudden.

Error:

[Microsoft.Uii.Customization.Plugin: Microsoft.Uii.Customization.Plugin.RestrictDataDuplicationPlugin]
[923014f5-390e-42be-95e4-17d6f90ff805: Microsoft.Uii.Customization.Plugin.RestrictDataDuplicationPlugin: Create of uii_hostedapplication]

Following is one useful article provided some insights into possible causes but it didn’t help us to fix the issue. However, we were confident that issue is around sandbox service.

https://community.dynamics.com/crm/b/crminthefield/archive/2013/01/29/the-plug-in-execution-failed-because-no-sandbox-hosts-are-currently-available

So, we restarted our sandbox and app servers which has fixed the issue in all except one organization. Then this question popped up “Why we need sandbox mode for Microsoft provided plugin in onprem environment?”. Answer – “Not Required..!!”. We then changed following UII and USD assembly isolation mode to “None”.

Plugin assemblies:

Microsoft.Uii.Customization.Plugin
Microsoft.Crm.UnifiedServiceDesk.Plugin

This solved our issue and we were able to create hosted controls in all of our orgs.  This may not be an actual fix for the root cause but a simple workaround for the issue(Remember it’s only for onprem).

Hope it helps..!!

How – To Series 18: Error – Failed to insert audit record

Hi all,
Recently, we were getting following error while trying to work with any CRM record in our non-prod environment. Prior to that day, we bulk updated huge set of records (~300K) which might have contributed to this issue.

          We started looking into CRM Trace Logs and found following error info. This info doesn’t give much info to resolve the issue.

“Failed to insert audit record”

 [Microsoft.Crm.Audit: Microsoft.Crm.AuditCreateUpdatePlugin]

         We then looked into SQL Logs and found following error info:

The transaction log for database ‘ORG_MSCRM” is full due to ‘LOG_BACKUP’.

This error info helped us in resolving the issue by increasing the disk space for the log file.  It indicated bulk updates in CRM resulted in rapid growth of Log file of the CRM ORG DB.

Hope this helps..!!

How – To Series 17: MS CRM 2013 Installation Errors

Hi all,
I was trying to do In place upgrade for MS CRM 2013 on my test server and I was getting following errors1. Setup asking for Reboot even after couple of restarts.“Error| Setup cannot continue because there is a pending restart required. Restart the computer and then try running Setup again.”


I went through Error Log located @ C:Users<User>AppDataRoamingMicrosoftMSCRMLogs


Following is the error log which says SkyDrive requires an uninstall. Even after uninstalling Skydrive explicitly the problem persists.

03:40:11|  Error| Reboot required — Key Found: HKCUSoftwareMicrosoftWindowsCurrentVersionRunOnce contains Uninstall C:UsersAdministratorAppDataLocalMicrosoftSkyDrive16.4.6010.0727
03:40:14|  Error| Setup cannot continue because there is a pending restart required. Restart the computer and then try running Setup again., Error, OK

03:40:14|   Info| InputResult: OK


Solution:
Following Support Article helped resolving the issue. Article talks about “MS CRM for Outlook” installation. However, this applies to MS CRM installation also.
We just need to remove registry keys mentioned under the specified registry hive. This resolves the problem.
http://support.microsoft.com/kb/2004114

2. Setup System check fails as the selected CRM 2011 organization for upgrade is using CRM 4.0 end points in web resources.

Solution:
We need to upgrade legacy code first in order to do the installation. So, it looks like Microsoft made it mandatory for the code upgrade from unsupported code(may be not every unsupported code). As it is a test server(Lazy enough to find an excuse), I have opted a Vanilla CRM Organization for the upgrade.

Hope it helps…!!! 🙂