How to use Kendo UI DataSource, Kendo UI Grid with Dynamics CRM 2011 REST Endpoint Sample 1

The complete source code can be download from sample gallery: Kendo UI DataSource, Kendo UI Grid with Dynamics CRM 2011 REST Endpoint

Kendo UI is a HTML5, jQuery-based framework for building modern HTML apps. Kendo UI combines the best of emerging HTML5, CSS3, and JavaScript technologies with robust, cross-browser techniques to deliver a framework that is both powerfully rich and broadly compatible with older browsers.

Microsoft Dynamics CRM 2011 Release 8 include support for IE, Chrome, Firefox and Safari.

By using a powerful JavaScript framework, the use of ASP.NET is reduced which would remove the complexity of deployment which also makes the solution much more portable.

By using Kendo UI DataSource, Kendo UI Grid with Dynamics CRM 2011 REST Endpoint, it is easy to implement server side filter, sorting and paging.

There are a few tricks to use Kendo UI together with Dynamics CRM 2011 I want to share. The complete code is in the end of this post.

1. Microsoft Dynamics CRM 2011 REST Endpoint does not support $format system query option.

You can use a global jQuery ajax event handler to specifying http header ensures that the results will be returned as JSON.


$(document).ajaxSend(function (e, jqxhr, settings) {
 if (settings.url.toLowerCase().indexOf("XRMServices/2011/OrganizationData.svc".toLowerCase()) >= 0) {
 jqxhr.setRequestHeader("Accept", "application/json");
 }
});

2. Microsoft Dynamics CRM 2011 REST Endpoint does not support $inlinecount, $count system query option.

you can set a function to use fetch xml and soap endpoint to return the total record count.


var dataSource = new kendo.data.DataSource({
 schema: {
 total: function (data) {
var fetchXml = '<fetch mapping="logical" aggregate="true">' +
 '<entity name="account">' +
 "<attribute name='accountid' alias='count' aggregate='count'/>" +
 '</entity>' +
 '</fetch>';

var context = GetGlobalContext();

var serverUrl = window.parent.document.location.protocol + '//' + window.parent.document.location.host + '/' + context.getOrgUniqueName();

var _oService = new FetchUtil(context.getOrgUniqueName(), serverUrl);
 var res = _oService.Fetch(fetchXml);

var count = res[0].attributes.count.formattedValue;

return count;
 }
 }
});

3. you need to stop Kendo UI to send default query string parameter and use Dynamics CRM 2011 specific format


var dataSource = new kendo.data.DataSource({
 transport: {
 parameterMap: function (options) {
 return {
 $top: options.take,
 $skip: options.skip,
 $select: 'Name,Telephone1,Address1_City'',
 $orderby: options.sort[0].field + ' ' + options.sort[0].dir
 };
 }
 }
});

4. you have to parse returned json object to return object array that Kendo UI understand


var dataSource = new kendo.data.DataSource({
 schema: {
 parse: function (data) {
 return data.d.results;
 },
 type: "json"
 }
});

Complete Kendo UI DataSource code

var context = GetGlobalContext();

var serverUrl = window.parent.document.location.protocol + '//' + window.parent.document.location.host + '/' + context.getOrgUniqueName();

dataSource = new kendo.data.DataSource({
 transport: {
 read: {
 url: serverUrl + "/XRMServices/2011/OrganizationData.svc/AccountSet",
 dataType: 'json'
 },
 parameterMap: function (options) {
 var parameter = {
 $top: options.take,
 $skip: options.skip,
 $select: 'Name,Telephone1,Address1_City',
 $orderby: options.sort[0].field + ' ' + options.sort[0].dir
 };

return parameter;
 }
 },
 schema: {
 model: {
 id: "AccountId",
 fields: {
 Name: { type: "string" },
 Telephone1: { type: "string" },
 Address1_City: { type: "string" }
 }
 },
 total: function (data) {

var fetchXml = '<fetch mapping="logical" aggregate="true">' +
 '<entity name="account">' +
 "<attribute name='accountid' alias='count' aggregate='count'/>" +
 '</entity>' +
 '</fetch>';

var _oService = new FetchUtil(context.getOrgUniqueName(), serverUrl);
 var res = _oService.Fetch(fetchXml);

var count = res[0].attributes.count.formattedValue;

return count;
 },
 parse: function (data) {
 return data.d.results;
 },
 type: "json"
 },
 serverPaging: true,
 pageSize: 10,
 serverSorting: true,
 sort: { field: "Name", dir: "asc" }
 });

Complete Sample code

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
 <meta charset="utf-8" />
 <title>Kendo UI Demo</title>
 <!-- CDN-based stylesheet references for Kendo UI Web -->
 <link rel="stylesheet" href="http://cdn.kendostatic.com/2012.2.913/styles/kendo.common.min.css" />
 <link rel="stylesheet" href="http://cdn.kendostatic.com/2012.2.913/styles/kendo.default.min.css" />
</head>
<body style="border-width: 0px; padding-left: 0px; padding-top: 0px; margin-left: 0px; margin-top: 0px; margin-bottom: 0px; margin-right: 0px">
 <div id="grid"></div>
 <!-- CDN-based script reference for jQuery -->
 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
 <!-- CDN-based script reference for Kendo UI DataViz -->
 <script type="text/javascript" src="http://cdn.kendostatic.com/2012.2.913/js/kendo.web.min.js"></script>
 <script type="text/javascript" src="ClientGlobalContext.js.aspx"></script>
 <script type="text/javascript">
 $(document).ready(function ($) {

var context = GetGlobalContext();

var serverUrl = window.parent.document.location.protocol + '//' + window.parent.document.location.host + '/' + context.getOrgUniqueName();

dataSource = new kendo.data.DataSource({
 transport: {
 read: {
 url: serverUrl + "/XRMServices/2011/OrganizationData.svc/AccountSet",
 dataType: 'json'
 },
 parameterMap: function (options) {
 var parameter = {
 $top: options.take,
 $skip: options.skip,
 $select: 'Name,Telephone1,Address1_City',
 $orderby: options.sort[0].field + ' ' + options.sort[0].dir
 };

return parameter;
 }
 },
 schema: {
 model: {
 id: "AccountId",
 fields: {
 Name: { type: "string" },
 Telephone1: { type: "string" },
 Address1_City: { type: "string" }
 }
 },
 total: function (data) {

var fetchXml = '<fetch mapping="logical" aggregate="true">' +
 '<entity name="account">' +
 "<attribute name='accountid' alias='count' aggregate='count'/>" +
 '</entity>' +
 '</fetch>';

var _oService = new FetchUtil(context.getOrgUniqueName(), serverUrl);
 var res = _oService.Fetch(fetchXml);

var count = res[0].attributes.count.formattedValue;

return count;
 },
 parse: function (data) {
 return data.d.results;
 },
 type: "json"
 },
 serverPaging: true,
 pageSize: 10,
 serverSorting: true,
 sort: { field: "Name", dir: "asc" }
 });

var grid = $("#grid").kendoGrid({
 dataSource: dataSource,
 height: 450,
 columns: [{
 template: '<input type="checkbox" />',
 sortable: false,
 width: 45
 },
 {
 title: 'Account Name',
 field: "Name",
 width: 180
 },
 {
 title: 'Main Phone',
 field: "Telephone1",
 width: 300
 },
 {
 title: 'Address 1: City',
 field: "Address1_City",
 filterable: false,
 width: 300,
 sortable: false
 }],
 editable: false,
 pageable: true,
 selectable: "multiple, row",
 sortable: {
 mode: 'single',
 allowUnsort: false
 },
 dataBound: function () {
 grid.table.find("tr").find("td:first input")
 .change(function (e) {
 if (!$(this).prop('checked')) {
 grid.clearSelection();
 }
 });
 },
 change: function (e) {
 window.setTimeout(function () {
 var checkbox = $(e.sender.select()[0]).find("td:first input").prop("checked", true);
 grid.table.find("tr").find("td:first input:checked").not(checkbox).prop("checked", false);
 }, 0);
 }
 }).data("kendoGrid");

grid.thead.find("th:first")
 .append($('<input class="selectAll" type="checkbox"/>'))
 .delegate(".selectAll", "click", function () {
 var checkbox = $(this);

grid.table.find("tr")
 .find("td:first input")
 .attr("checked", checkbox.is(":checked"))
 .trigger("change");
 });
 }).ajaxSend(function (e, jqxhr, settings) {
 if (settings.url.toLowerCase().indexOf("XRMServices/2011/OrganizationData.svc".toLowerCase()) >= 0) {
 jqxhr.setRequestHeader("Accept", "application/json");
 }
 });
 </script>
 <script id="FetchUtil" type="text/javascript">
 var XMLHTTPSUCCESS = 200;
 var XMLHTTPREADY = 4;

function FetchUtil(sOrg, sServer) {
 this.org = sOrg;
 this.server = sServer;

if (sOrg == null) {
 if (typeof (ORG_UNIQUE_NAME) != "undefined") {
 this.org = ORG_UNIQUE_NAME;
 }
 }

if (sServer == null) {
 this.server = window.location.protocol + "//" + window.location.host;
 }
 }

FetchUtil.prototype._ExecuteRequest = function (sXml, sMessage, fInternalCallback, fUserCallback) {
 var xmlhttp = new XMLHttpRequest();
 xmlhttp.open("POST", this.server + "/XRMServices/2011/Organization.svc/web", (fUserCallback != null));
 xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
 xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
 xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");

if (fUserCallback != null) {
 //asynchronous: register callback function, then send the request.
 var crmServiceObject = this;
 xmlhttp.onreadystatechange = function () {
 fInternalCallback.call(crmServiceObject, xmlhttp, fUserCallback)
 };
 xmlhttp.send(sXml);
 } else {
 //synchronous: send request, then call the callback function directly
 xmlhttp.send(sXml);
 return fInternalCallback.call(this, xmlhttp, null);
 }
 }

FetchUtil.prototype._HandleErrors = function (xmlhttp) {
 /// <summary>(private) Handles xmlhttp errors</summary>
 if (xmlhttp.status != XMLHTTPSUCCESS) {
 var sError = "Error: " + xmlhttp.responseText + " " + xmlhttp.statusText;
 alert(sError);
 return true;
 } else {
 return false;
 }
 }

FetchUtil.prototype.Fetch = function (sFetchXml, fCallback) {
 /// <summary>Execute a FetchXml request. (result is the response XML)</summary>
 /// <param name=”sFetchXml”>fetchxml string</param>
 /// <param name=”fCallback” optional=”true” type=”function”>(Optional) Async callback function if specified. If left null, function is synchronous </param>

var request = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
 request += "<s:Body>";

request += '<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' + '<request i:type="b:RetrieveMultipleRequest" ' + ' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' + ' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' + '<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' + '<b:KeyValuePairOfstringanyType>' + '<c:key>Query</c:key>' + '<c:value i:type="b:FetchExpression">' + '<b:Query>';

request += CrmEncodeDecode.CrmXmlEncode(sFetchXml);

request += '</b:Query>' + '</c:value>' + '</b:KeyValuePairOfstringanyType>' + '</b:Parameters>' + '<b:RequestId i:nil="true"/>' + '<b:RequestName>RetrieveMultiple</b:RequestName>' + '</request>' + '</Execute>';

request += '</s:Body></s:Envelope>';

return this._ExecuteRequest(request, "Fetch", this._FetchCallback, fCallback);
 }

FetchUtil.prototype._FetchCallback = function (xmlhttp, callback) {
 ///<summary>(private) Fetch message callback.</summary>
 //xmlhttp must be completed
 if (xmlhttp.readyState != XMLHTTPREADY) {
 return;
 }

//check for server errors
 if (this._HandleErrors(xmlhttp)) {
 return;
 }

var sFetchResult = xmlhttp.responseXML.selectSingleNode("//a:Entities").xml;

var resultDoc = new ActiveXObject("Microsoft.XMLDOM");
 resultDoc.async = false;
 resultDoc.loadXML(sFetchResult);

//parse result xml into array of jsDynamicEntity objects
 var results = new Array(resultDoc.firstChild.childNodes.length);

for (var i = 0; i < resultDoc.firstChild.childNodes.length; i++) {
 var oResultNode = resultDoc.firstChild.childNodes[i];
 var jDE = new jsDynamicEntity();
 var obj = new Object();

for (var j = 0; j < oResultNode.childNodes.length; j++) {
 switch (oResultNode.childNodes[j].baseName) {
 case "Attributes":
 var attr = oResultNode.childNodes[j];

for (var k = 0; k < attr.childNodes.length; k++) {

// Establish the Key for the Attribute
 var sKey = attr.childNodes[k].firstChild.text;
 var sType = '';

// Determine the Type of Attribute value we should expect
 for (var l = 0; l < attr.childNodes[k].childNodes[1].attributes.length; l++) {
 if (attr.childNodes[k].childNodes[1].attributes[l].baseName == 'type') {
 sType = attr.childNodes[k].childNodes[1].attributes[l].text;
 }
 }

switch (sType) {
 case "a:OptionSetValue":
 var entOSV = new jsOptionSetValue();
 entOSV.type = sType;
 entOSV.value = attr.childNodes[k].childNodes[1].text;
 obj[sKey] = entOSV;
 break;

case "a:EntityReference":
 var entRef = new jsEntityReference();
 entRef.type = sType;
 entRef.guid = attr.childNodes[k].childNodes[1].childNodes[0].text;
 entRef.logicalName = attr.childNodes[k].childNodes[1].childNodes[1].text;
 entRef.name = attr.childNodes[k].childNodes[1].childNodes[2].text;
 obj[sKey] = entRef;
 break;

default:
 var entCV = new jsCrmValue();
 entCV.type = sType;
 entCV.value = attr.childNodes[k].childNodes[1].text;
 obj[sKey] = entCV;

break;
 }
 }

jDE.attributes = obj;
 break;

case "Id":
 jDE.guid = oResultNode.childNodes[j].text;
 break;

case "LogicalName":
 jDE.logicalName = oResultNode.childNodes[j].text;
 break;

case "FormattedValues":
 var foVal = oResultNode.childNodes[j];

for (var k = 0; k < foVal.childNodes.length; k++) {
 // Establish the Key, we are going to fill in the formatted value of the already found attribute
 var sKey = foVal.childNodes[k].firstChild.text;

jDE.attributes[sKey].formattedValue = foVal.childNodes[k].childNodes[1].text;
 }
 break;
 }
 }
 results[i] = jDE;
 }

//return entities
 if (callback != null) callback(results);
 else return results;
 }

function jsDynamicEntity(gID, sLogicalName) {
 this.guid = gID;
 this.logicalName = sLogicalName;
 this.attributes = new Object();
 }

function jsCrmValue(sType, sValue) {
 this.type = sType;
 this.value = sValue;
 }

function jsEntityReference(gID, sLogicalName, sName) {
 this.guid = gID;
 this.logicalName = sLogicalName;
 this.name = sName;
 this.type = 'EntityReference';
 }

function jsOptionSetValue(iValue, sFormattedValue) {
 this.value = iValue;
 this.formattedValue = sFormattedValue;
 this.type = 'OptionSetValue';
 }
</script>
</body>
</html>

Dynamics CRM 2011 publish customizations failed after successful import

I have run into this problem before, we can successfully import one solution package but the publish failed with a generic error.

This is because the solution package is large and the SQL Server is slow and someone enabled CRM Server tracing.

I have find a solution from this link: http://blogs.msdn.com/b/darrenliu/archive/2011/07/08/crm-2011-cannot-publish-customizations-after-import.aspx

The cause for this problem is mentioned in that blog: “CRM has a default timeout value of 300 seconds = 5 minutes. If any process takes more than 5 minutes, it’ll stop.”

This is also mentioned in the SDK as well. http://msdn.microsoft.com/en-us/library/gg334495.aspx#BKMK_MaxSizeOfSolution

Change the maximum allowed size by editing the <httpRuntime> element in the web.config file for the application. Edit the executionTimeout and maxRequestLength attributes to allow for the necessary size.

The solution mentioned in the blog will solve the problem as well, but it is a bit outdated.

Dynamics CRM 2011 web.config file use location element, so we can fix the problem by update root web.config file.

One of the fix mentioned in that post might be already fix depend on your Roll Up version.

The complete steps are listed below:

  1. On the CRM application server, open Internet Information Services (IIS) Manager.
  2. Expand the server name, and then expand Web Sites.
  3. Right-click the Microsoft CRM Web site, and then click Open.
  4. Right-click the Web.config file, click Open With, and then click Notepad.
  5. In Notepad, find

<system.web>
 <httpRuntime executionTimeout="300" maxRequestLength="32768" requestValidationMode="3.0" encoderType="Microsoft.Crm.CrmHttpEncoder, Microsoft.Crm" />

change to

 <system.web>
 <httpRuntime executionTimeout="300" maxRequestLength="32768" requestValidationMode="3.0" encoderType="Microsoft.Crm.CrmHttpEncoder, Microsoft.Crm" />

find

 <location path="MSCRMServices">
 <system.web>
 <httpRuntime maxRequestLength="8192" />

change to

 <location path="MSCRMServices">
 <system.web>
 <httpRuntime maxRequestLength="32768" />

Dynamics CRM 2011 update help server url

PowerShell


PS > Add-PSSnapin Microsoft.Crm.PowerShell
 PS > $web = Get-CrmSetting WebAddressSettings
 PS > $web.HelpServerUrl = ""
 PS > Set-CrmSetting $web

C#


var service = Microsoft.Xrm.Sdk.Deployment.Proxy.ProxyClientHelper.CreateClient(new System.Uri("http://crm2011:5555/XRMDeployment/2011/Deployment.svc"));

var webAddressSettings = (Microsoft.Xrm.Sdk.Deployment.WebAddressSettings)service.Retrieve(Microsoft.Xrm.Sdk.Deployment.DeploymentEntityType.WebAddressSettings, null);

webAddressSettings.HelpServerUrl = "http://crm2011:5555";

service.Update(webAddressSettings);