Friday, April 15, 2016

Dynamics CRM 2016 and Use Legacy form rendering

So you may know that Microsoft has improved form load by doing some extensive caching.  There is a setting under General Settings called "Use Legacy form rendering" which you can set to Yes or No.

Now you might wonder why I am writing a post about this setting. Well it was a JavaScript  error on a Form Load that I was getting. I had my own JavaScript code on the Form Load  but the error shown was confusing:
Field:window
Event:onload
Error:Unable to get property '$o_3' of undefined or null reference

Huh? A bit of Googling showed that others had come across it to


The post included this comment "Note that this error only occurs when you turn on the “Use Legacy form rendering”.  Sure enough changing this setting to No avoids the error. 

But what if you want to preserve the faster form rendering?  Patric provide the solution in his blog. 
Add the following line before calling the prefilter function.

Xrm.Page.getControl(“EntityName”)._control && Xrm.Page.getControl(“EntityName”)._control.tryCompleteOnDemandInitialization && Xrm.Page.getControl(“EntityName”)._control.tryCompleteOnDemandInitialization();

Brilliant. 

Dynamics CRM 2016 and Legacy Entities

This is not the first time I've run into a problem with so called "Legacy Entities". I am referring to the entities that have been left out in the cold since CRM 2013. These include
  • address
  • opportunity product
  • quote product
  • order product
  • invoice product

There are some things that these entities don't support which include
  • Accessible in the Tablet App
  • Update attributes with workflows
  • Business Rules (though oddly Opportunity product does support business rules)
  • Old style forms

I have also come across with problems when upgrading to CRM 2016. This problem occurred on Opportunity Products form which had been customised in CRM 2015. I removed a number of fields I didn't need and it was working fine. 
After the upgrade to 2016 though I was getting this JavaScript error on form load

Field:window
Event:onload
Error:Unable to get property 'addOnChange' of undefined or null reference

Now that was not from any JavaScript code that I had but from a Microsoft library called OpportunityProduct_main_system_library.js

When I looked into the code I could see that it was referring to attributes that were not on the form:
Manual discount amount
Tax

When I added these back onto the form and hid them, the error went away. 

Moral of the story: Don't delete attributes from legacy forms - just hide them. 

Saturday, December 12, 2015

Dependent (Linked) OptionSets in Dynamics CRM 2015

I recently used the sample code provided in the SDK that allows you to have multiple OptionSets linked together so that selecting an item in the first OptionSet will filter the items appearing in the second. 

https://msdn.microsoft.com/en-us/library/gg594433(v=crm.7).aspx

There was an error in the instructions for configuring the OnLoad event on the form. It says to use

"sample_TicketDependentOptionSetConfig.xml"

 as a parameter.  That's wrong. You need to use 

"sample_TicketDependentOptionSetConfig"

The SDK.DependentOptionSet.init function in the JavaScript file I modified slightly to support multiple languages.  The first few lines now read

//Retrieve the XML Web Resource specified by the parameter passed var clientURL = Xrm.Page.context.getClientUrl();

var userLcid = Xrm.Page.context.getUserLcid();

var pathToWR = clientURL + "/WebResources/" + webResourceName + "_" + userLcid;




The data files were then appended with the relevant LCID for each language 

e.g. "sample_TicketDependentOptionSetConfig_1033.xml"

A very cool and easy to use JavaScript Library. Thanks Microsoft. 

Sunday, November 29, 2015

ILMerge Command Line Syntax

I had cause the other day to use ILMerge to combine several DLLs. The first issue I came across was:

Unresolved assembly reference not allowed: System.Core.

This is mentioned in several blogs and the solution suggested was to use the /lib switch and specify the path to the .NET library you want for the target. In my case I wanted to target 4.5.2 so I used the following which supposedly worked (the blog said)

ilmerge  /lib:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2"   /out:MERGED.First.dll  First.dll Second.dll  /keyfile:key.snk

Note that if you strongly signed your First DLL don't assume the output will be strongly signed because it won't be. You have to add the /keyfile option to do that. 

Note also there are blogs that say don't try and point to C:\Windows... because that is not the correct location.  You need to use the Reference Assemblies path given above.

Well I didn't get the error and ILMerge started off and I waited. And waited. And waited.  So basically ILMerge just hangs still consuming by the way 50% CPU just to fool you into thinking its actually doing something. 

Its the wrong syntax. The correct syntax uses /targetplatform not /lib

ilmerge  /targetplatform:v4,"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2"   /out:MERGED.First.dll  First.dll Second.dll /keyfile:key.snk

Creating a SharePoint OnLine Folder using CSOM

Surprisingly I can't find a complete solution to this on the blogosphere. So let me put that right.

First add two references to
Microsoft.SharePoint.Client.dll
Microsoft.SharePoint.Client.RunTime.dll

I found them on my 64-bit server located here
C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI

In your code add a using statement

using Microsoft.SharePoint.Client;

Then in your method add the following

using (ClientContext ctx = new ClientContext(site))
{var securePassword = new SecureString();
foreach (char c in password)
{
    securePassword.AppendChar(c);
}
 
ctx.Credentials = new Microsoft.SharePoint.Client.SharePointOnlineCredentials(username, securePassword);
var list = ctx.Web.Lists.GetByTitle(doclib);
var folderRoot = list.RootFolder;
ctx.Load(folderRoot);
ctx.ExecuteQuery();
folderRoot = folderRoot.Folders.Add(folder);
ctx.ExecuteQuery();
};
 

Monday, August 31, 2015

Serialize a CRM Entity to XML in Sandbox mode Plugin

I have been struggling with this problem for a while. Previous posts have outlined the problem: how to get changes in CRM sent to Back Office systems as XML messages. When using CRM Online a big frustration is that you cannot serialize objects to XML because the WriteObject method of the serializer throws a security exception.

While you can post the Context of a plugin to the Azure Service Bus, you have to use the CRM SDK to interpret this and turn it into something useful. My previous post outlines how to do this with an Azure Worker Process.

(You could construct the XML message line by line using string builder but seriously who wants to do that?)

I found a simple solution which may solve the problem. It was this post that provided the inspiration. It describes how to turn FetchXML results into a data table.  Now FetchXML returns an entity collection and the code shows how to load that into a data table and it also handles the special CRM data types like Entity Reference. 

So there you are in a Sandbox plugin.  You have the Post-Image entity and you want to convert that into a nicely structured XML file that you can post to the Azure Service Bus.  You can use part of the code above (ignoring the FetchXML query) to convert the Post-Image to a data table.  I put it into a function I called ConvertEntityToDataTable.

Now getting XML is easy.

DataSet ds = new DataSet("Invoice");
DataTable dt = new DataTable("Attributes");
ConvertEntityToDataTable(dt, entity);
ds.Tables.Add(dt);

string xml = ds.GetXml();

The result looks like this (I've just given an extract)
<Invoice>
 <Attributes>
 <billto_city>London</billto_city>
 <billto_line1>12 Hgh Street</billto_line1>
 <billto_line2>Clapham Common</billto_line2>
 <billto_line3>Clapham</billto_line3>
 </Attributes>
</Invoice>


Remember that PostImages provide their attributes in alphabetical order which is great for mapping structured XML messages. The Target entity does not so you would need to address that.
The XML is missing some things I would need to add:
  (a) the xml declaration at the top specifying the encoding
  (b) a namespace to identify the message to an ESB

But I am delighted with the result because now I can construct proper XML messages in a Sandbox plugin. 

The two functions required are shown below
///////
         private void ConvertEntityToDataTable(DataTable dataTable, Entity entity)
        {
            DataRow row = dataTable.NewRow();
            foreach (var attribute in entity.Attributes)
            {
                if (!dataTable.Columns.Contains(attribute.Key))
                {
                    dataTable.Columns.Add(attribute.Key);
                }
                row[attribute.Key] = getAttributeValue(attribute.Value).ToString();
            }
            foreach (var fv in entity.FormattedValues)
            {
                if (!dataTable.Columns.Contains(fv.Key + "name"))
                {
                    dataTable.Columns.Add(fv.Key + "name");
                }
                row[fv.Key + "name"] = fv.Value;
            }
            dataTable.Rows.Add(row);
        }
///////
        private object getAttributeValue(object entityValue)
        {
            object output = "";
            switch (entityValue.ToString())
            {
                case "Microsoft.Xrm.Sdk.EntityReference":
                    output = ((EntityReference)entityValue).Name;
                    break;
                case "Microsoft.Xrm.Sdk.OptionSetValue":
                    output = ((OptionSetValue)entityValue).Value.ToString();
                    break;
                case "Microsoft.Xrm.Sdk.Money":
                    output = ((Money)entityValue).Value.ToString();
                    break;
                case "Microsoft.Xrm.Sdk.AliasedValue":
                    output = getAttributeValue(((Microsoft.Xrm.Sdk.AliasedValue)entityValue).Value);
                    break;
                default:
                    output = entityValue.ToString();
                    break;
            }
            return output;
        }
    }

Sunday, August 16, 2015

Dynamics CRM 2015 Online and Azure Service Bus Messages

If you've configured Dynamics CRM 2015 Online with Azure Service Bus you will know that the "message" it puts on the queue is the plugin execution context. This is the same context that you use within a plugin.  You will know that to do anything with the context you have to use the Microsoft.Xrm.Sdk and extract the Pre or Post Image and the so called Target image. "Target" is s stupid name I always think. It contains the delta - the attributes that were changed during the operation.  The Post Image of say an Update event will contain all the attributes that have values in it and excludes any attributes with null values. 

The reason for using the Azure Service Bus is usually to get data from CRM to your on-premise systems and more often than not you may be using an ESB to read messages from the Azure Service Bus.  BizTalk for example has an adaptor that you just need to configure to read messages from Azure Service Bus.  However any BizTalk developer is going to be very disappointed if you provide them with just a Plugin Execution Context because they will have to use the CRM SDK to convert it into an XML message.  Even then the entity objects when serialized will result in a collection of KeyValuePairs.  The biggest problem with that is trying to map it to a strongly typed schema particularly because the structure varies so much when attributes that are null are set to a value and vice versa.  I've tried using the BizTalk mapper and failed.

UPDATE: CRM Online restricts plugins to Sandbox mode and you cannot serialize objects to XML because this is prohibited. I have recently stumbled on a way of getting round this. This may provide a much simpler solution then described in the rest of this article.

One solution is to have custom code that will read the context, convert it to a strongly typed schema and then put it back in another queue.  This needs to be done serially to ensure that Ordered Delivery is maintained but at least it gets to a schema that is worthy of the name.

Now you have to be careful when using CRM Online because if your plugins consume a lot of CPU they will be terminated with extreme prejudice.  It is best to keep the code within your plugin as minimal as possible and then have the bulk of your code execute on an Azure VM where you don't need to worry about CPU usage because you can size the VM to suit.

The best way to architect this is
a) Have a Plugin that sends the Plugin Execution Context to a Azure Service Bus Queue
b) Create an Azure Worker Process that reads messages from the Queue, creates an XML message and puts it into another Azure Service Bus Queue (be sure to maintain the order)
c) If using BizTalk create a Receive Port that will read XML messages from the second Queue

The Azure Worker Process is a lot like a Windows Service (in Azure its called a Cloud Service) and will automatically restart if it is stopped.  When you deploy the Azure Worker Process it will create its own VM.  You can supply configuration settings which in our case would include the EndPoint of the Queue we are reading from and the EndPoint of the Queue we are writing to.

While you messages should be placed on the first queue using a plugin running synchronously, the Azure Worker Process is running asynchronously.  If you want to post messages back to CRM because of a failure then you need to setup another asynchronous process to send the messages back to CRM.