Bug: EntityReference, Create and latebinding

For big projects and large code sizes, it is recommended to use strongly typed classes when you are dealing with CRUD operations related to the CRM entities. You can use Early Bound Generator or “crmsvcutil” to do this. The advantages are compile type errors for mismatches types and intellisense. But if you are quickly testing out some code, latebinding is the easiest path, as it doesn’t involve the overhead of generating the classes. During this process, I discovered a bug.

Here is the code and I will explain the bug after that.

            CrmServiceClient crmSvc = new CrmServiceClient(ConfigurationManager.ConnectionStrings["CRMConnectionString"].ConnectionString);
            if (crmSvc.IsReady)
            {
                #region create contact with account entityreference

                var account = new Entity("account");
                account["name"] = "Test Account";
                account.Id = crmSvc.OrganizationServiceProxy.Create(account);
                Console.WriteLine($"Created account with id {account.Id}");

                Console.WriteLine("Creating contact with account as EntityReference");
                Console.WriteLine("----------------------------------------------------");

                var contact = new Entity("contact");
                contact["parentcustomerid"] = account.ToEntityReference();
                contact.Id = crmSvc.OrganizationServiceProxy.Create(contact);
                Console.WriteLine($"Created contact with id {contact.Id}");

                contact = crmSvc.OrganizationServiceProxy.Retrieve("contact", contact.Id, new ColumnSet("parentcustomerid"));
                Console.WriteLine($"Contact: Account Lookup {contact.GetAttributeValue<EntityReference>("parentcustomerid")?.Id}");

                #endregion

                #region update contact with account Guid

                Console.WriteLine("Updating contact with account as Guid");
                Console.WriteLine("----------------------------------------------------");
                try
                {
                    contact["parentcustomerid"] = account.Id;
                    crmSvc.OrganizationServiceProxy.Update(contact);
                    var updateContact = crmSvc.OrganizationServiceProxy.Retrieve("contact", contact.Id, new ColumnSet("parentcustomerid"));
                    Console.WriteLine($"Updated Contact: Account Lookup {updateContact.GetAttributeValue<EntityReference>("parentcustomerid")?.Id}");
                }
                catch (Exception e)
                {
                    Console.WriteLine($"Update Contact failed with Exception: {e.Message}");
                }

                crmSvc.OrganizationServiceProxy.Delete("contact", contact.Id);
                Console.WriteLine($"Deleted contact with id {contact.Id}");

                #endregion

                #region create contact with account Guid

                Console.WriteLine("\nCreating contact with account as Guid");
                Console.WriteLine("----------------------------------------------------");
                contact = new Entity("contact");
                contact["parentcustomerid"] = account.Id;
                //This should not succeed
                contact.Id = crmSvc.OrganizationServiceProxy.Create(contact);
                Console.WriteLine($"Created contact with id {contact.Id}");

                contact = crmSvc.OrganizationServiceProxy.Retrieve("contact", contact.Id, new ColumnSet("parentcustomerid"));
                Console.WriteLine($"Contact: Account Lookup {contact.GetAttributeValue<EntityReference>("parentcustomerid")?.Id}");

                crmSvc.OrganizationServiceProxy.Delete("contact", contact.Id);
                Console.WriteLine($"Deleted contact with id {contact.Id}");

                crmSvc.OrganizationServiceProxy.Delete("account", account.Id);
                Console.WriteLine($"\nDeleted account with id {account.Id}");

                #endregion

            }

Here is the result.

Guid Entityreference result

Even if you are using latebinding, CRM webservice respects type and doesn’t allow you to set an attribute to a type, that doesn’t match its definition, except in one case: You can set the EntityReference attribute to a Guid, and the code with execute without any exception. The good thing though is the fields is just set to null, and the Guid is ignored, but this a bug as you cannot do it with other types e.g. you can’t set an Optionset field to an int or a Currency field to a decimal.

I have logged this on Connect for verification. I experienced this behaviour in CRMOnline and CRM2015.

Connect issue -> https://connect.microsoft.com/site687/feedback/details/2740732/sdk-permits-setting-entityreference-attribute-to-guid-late-binding

Further reading:

MSDN: Create, retrieve, update, and delete (late bound)

 

Using Stylish to enhance CRM

What is Stylish?

Stylish is a browser extension available for both Firefox and Chrome. It helps you to alter the styles on any site that you don’t control. The change is localised i.e. it is visible only in your local machine.

How can I use this to enhance Dynamics CRM

I will give you three quick scenarios that you will find useful.

  1. Hide the new “friendly” and “informative” notification messages.
  2. Show the full width of a form label without truncation .
  3. Alter the navigation bar text and colour to easily differentiate various environments.

Hiding the notification messages

This is the CSS you’ll use to hide the notification messages. With this you can quick nuke all the messages, across all the environments you specify using the Stylish configuration. This can be done without going to “Settings” and turn each different notification classes (Get Outlook App, Get CRM App, Trial expiry, Mailbox notifications and Install Interactive Service Hub) off. The rule is for any site that ends with dynamics.com.

@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document domain("dynamics.com") {
  .crmAppMessageBar_green, .crmAppMessageBar {
    display: none !important;
  }
}

Before Stylish

Message Bar Before

After Stylish

Message Bar After

Show the full label width without truncation

Starting from CRM2015, labels are not fully displayed if they are too long. The standard approach is to wrap around, but that is not how it is in CRM. Instead, they are truncated. The workaround solution for this is to increase the section width, but there is a better solution. Using Stylish, you can change the CSS that controls this behaviour. Below is the CSS for this. The rule is for any site that ends with dynamics.com.

@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document domain("dynamics.com") {
  .ms-crm-InlineEditLabel, .ms-crm-InlineEditLabelText {
    white-space: normal !important;
  }
}

Before Stylish

Label Truncation Before

After Stylish

Label Truncation After

Altering navigation bar text and colour

If you are working across multiple environments, you will like this one. You can change the colour and text using CSS, so that you’ll know where you are, when are constantly switching tabs. I did an extension to do just the colour change, but Stylish does it much better, as it exposes the full power of CSS. You can obviously use themes in CRM2016 to do this. But with this approach:

  1. You can have a theme you like, that is different from the published theme
  2. CRM2015 users can get the same theming goodness

Here is the CSS, you can use for this. The style is just for the navigation bar. You can go crazy and change the form styles, command bar styles and what not.

@-moz-document url-prefix(https://[ENTER THE URL FOR THE RULE]) {
  #navBar {
    background: green !important;
  }
  .navTabLogoText:after {
    content: " DEV" !important;
  }
}

Before Stylish

Navigation Bar Before

After Stylish

Navigation Bar After

Here is some screenshots from Firefox on how to get to the Stylish menu to add custom styles. It is a little bit different in Chrome.

Stylish Menu

I have one rule to do with message bar hiding and preventing label truncation.

Stylish

Here are the links for downloading Stylish

Chrome users head to https://chrome.google.com/webstore/detail/stylish/fjnbnpbmkenffdnngjfgmeleoegfcffe

Firefox users head to https://addons.mozilla.org/en-US/firefox/addon/stylish/

IE users, head to https://www.google.com.au/intl/en/chrome/browser/desktop/index.html Smile

This is a really productive tool to have and is also free.

Bookmarklet: Display Optionset values

Unless you have generated strongly typed classes, looking up Optionset values for the selected value or any other value, is one of the most annoying things to do as a developer. You can use the bookmarklet below to display Optionset values, along with the Optionset text for all the Optionsets on a form. You can drag the minified source to your bookmark bar, for creating the bookmarklet.

This is how a sample contact form looks after executing the bookmarklet.

Altered Optionset

Unminified

(function () {
	var contentPanels = Array.from(document.querySelectorAll('iframe')).filter(function (d) {
			return d.style.visibility !== 'hidden'
		});
	if (contentPanels && contentPanels.length > 0) {
		var Xrm = contentPanels[0].contentWindow.Xrm;
		var frameDocument = contentPanels[0].contentWindow.document;
		Xrm.Page.ui.controls.forEach(function (c) {
			if (c.getControlType() !== 'optionset')
				return;
			var attribute = c.getAttribute();
			var selectedOptionValue = attribute.getValue();
			var options = attribute.getOptions();
			var isClearOptions = options.some(function (o) {
					return o.text.indexOf(' (') === -1;
				});
			if (isClearOptions) {
				c.clearOptions();
			}
			options.forEach(function (o) {
				if (o.text && o.text.indexOf(' (') === -1) {
					o.text = o.text + ' (' + o.value + ')';
				}
				c.addOption(o);
			});
			if (selectedOptionValue && isClearOptions) {
				attribute.setValue(selectedOptionValue);
			}
		});
	} else {
		alert('Entity form not detected');
	}
})();

Minified

javascript:(function(){var contentPanels=Array.from(document.querySelectorAll('iframe')).filter(function(d){return d.style.visibility!=='hidden'});if(contentPanels&&contentPanels.length>0){var Xrm=contentPanels[0].contentWindow.Xrm;var frameDocument=contentPanels[0].contentWindow.document;Xrm.Page.ui.controls.forEach(function(c){if(c.getControlType()!=='optionset')
return;var attribute=c.getAttribute();var selectedOptionValue=attribute.getValue();var options=attribute.getOptions();var isClearOptions=options.some(function(o){return o.text.indexOf(' (')===-1;});if(isClearOptions){c.clearOptions();}
options.forEach(function(o){if(o.text&&o.text.indexOf(' (')===-1){o.text=o.text+' ('+o.value+')';}
c.addOption(o);});if(selectedOptionValue&&isClearOptions){attribute.setValue(selectedOptionValue);}});}else{alert('Entity form not detected');}})();void 0;

Bookmarklet: Clone Current Record

Sometimes, you just want to clone a current record, and slightly change the details. e.g. some sort of config entity record. You can use the bookmarklet below, that does cloning only at the parent entity level. Once the new form is open you can change the details and save it. Drag the minified code to your bookmarklet bar, and execute it when you have the CRM record open. The bookmarklet uses query parameters to populate the fields in the new form.

Unminified

(function() {
    var contentPanels = Array.from(document.querySelectorAll('iframe')).filter(function (d) {
        return d.style.visibility !== 'hidden'
      });

    if (!contentPanels || contentPanels.length == 0 || !contentPanels[0].contentWindow.Xrm) {
		alert('CRM Form not detected');
		return;
    }
	var Xrm = contentPanels[0].contentWindow.Xrm;
	var extraq = '';
	var clientUrl = Xrm.Page.context.getClientUrl();
	var entityName = Xrm.Page.data.entity.getEntityName();

	Xrm.Page.data.entity.attributes.forEach(function(c){
		var attributeType = c.getAttributeType();
		var attributeName = c.getName();
		var attributeValue = c.getValue();
		if(!attributeValue ||
		attributeName === 'createdon' ||
		attributeName === 'modifiedon' ||
		attributeName === 'createdby' ||
		attributeName === 'modifiedby') return;

		if(attributeType === 'lookup'){
			extraq += (attributeName+'name='+attributeValue[0].name+'&');
			extraq += (attributeName+'type='+attributeValue[0].entityType+'&');
			attributeValue = attributeValue[0].id;
		}
		if(attributeType === 'datetime'){
			attributeValue = attributeValue.toDateString();
		}
		extraq += (attributeName+'='+attributeValue+'&');
	});

	var newWindowUrl = clientUrl+'/main.aspx?etn='+entityName+'&pagetype=entityrecord'+'&extraqs=?'+encodeURIComponent(extraq)+'etn='+entityName;

	window.open(newWindowUrl, '_blank', "location=no,menubar=no,status=no,toolbar=no", false);
})();

Minified

javascript:(function(){var contentPanels=Array.from(document.querySelectorAll('iframe')).filter(function(d){return d.style.visibility!=='hidden'});if(!contentPanels||contentPanels.length==0||!contentPanels[0].contentWindow.Xrm){alert('CRM Form not detected');return;}
var Xrm=contentPanels[0].contentWindow.Xrm;var extraq='';var clientUrl=Xrm.Page.context.getClientUrl();var entityName=Xrm.Page.data.entity.getEntityName();Xrm.Page.data.entity.attributes.forEach(function(c){var attributeType=c.getAttributeType();var attributeName=c.getName();var attributeValue=c.getValue();if(!attributeValue||attributeName==='createdon'||attributeName==='modifiedon'||attributeName==='createdby'||attributeName==='modifiedby')return;if(attributeType==='lookup'){extraq+=(attributeName+'name='+attributeValue[0].name+'&');extraq+=(attributeName+'type='+attributeValue[0].entityType+'&');attributeValue=attributeValue[0].id;}
if(attributeType==='datetime'){attributeValue=attributeValue.toDateString();}
extraq+=(attributeName+'='+attributeValue+'&');});var newWindowUrl=clientUrl+'/main.aspx?etn='+entityName+'&pagetype=entityrecord'+'&extraqs=?'+encodeURIComponent(extraq)+'etn='+entityName;window.open(newWindowUrl,'_blank',"location=no,menubar=no,status=no,toolbar=no",false);})(); void 0;

I tested this only in CRMOnline on version 8.1.0.359.

End note:

This bookmarklet does not copy across the time component, if you have a datetime field with both date and time on the parent form.

Reference:

https://msdn.microsoft.com/en-us/library/gg334375.aspx#BKMK_ExampleXrmUtilityOpentEntityForm

Level Up: A Chrome extension for CRM Power Users

EDIT (14/11/2016): The extension is now available on Edge as well. Refer https://github.com/rajyraman/Level-up-for-Dynamics-CRM-365-Edge-Browser

Today, I released a new Chrome extension that assists CRM power users. In this initial release there are 12 quick functionalities:

Form helpers

  1. Display Logical names for controls (Original script by Chris Groh http://us.hitachi-solutions.com/blog/2014/10/27/showing-entity-logical-names-on-form/)
  2. God Mode (based on http://www.magnetismsolutions.com/blog/paulnieuwelaar/2014/07/29/activate-god-mode-in-crm-2013—don-t-let-your-users-see-this)
  3. Form properties (Original script by Jared Johnson http://www.magnetismsolutions.com/blog/jaredjohnson/2014/08/03/dynamics-crm-2013-resurrecting-the-form-properties-window-with-bookmarklet)
  4. Dirty fields i.e. fields that have been modified
  5. Display record URL
  6. Display record Id

Navigation helpers

  1. Open record by Id
  2. Open security area
  3. Open System Jobs
  4. Open Solutions
  5. Open Process Definitions
  6. Open main

How do you install it

Download the extension from Chrome store -> Level up for Dynamics CRM

How does it look

Once you install this extension you will see a new rocket icon in the toolbar. If you click the icon, you will get a popup menu.

Screenshot Main

How to use it

You can have a look at this animation below to get an idea about the functionalities.

Functionality Quick Intro

Source code

The source code for the extension can be downloaded from https://github.com/rajyraman/Levelup-for-Dynamics-CRM/

The minimum supported version of Dynamics CRM for this extension is CRM2015.

In the initial version, I have 12 quick actions. Please feel free to fork the repo and add new functionalities that you are currently using with a bookmarklet and finding it useful.

Please also log any issues/feedback in the github repo. I hope this will be a useful extension for improving your productivity.

EDIT (24/06/2016): Fixed credit links for God Mode, Form Properties and Control Logical Names bookmarklets in post and repo.