FetchXML + jQuery in a Dynamics CRM 2011 web resource
Over the weekend I started looking at a hobby project that involved querying and working with aggregate data from Dynamics CRM 2011 inside a hosted web resource using jQuery. Initially had I planned to use the OData/REST endpoint since that is much sexier than SOAP lately, but after a quick web search I realized that OData doesn't support "group by" queries, so that left me looking at FetchXML. As I mentioned in a post last month, I've generally avoided working with FetchXML, so I wasn't even sure where to get started to use it inside JavaScript. After another quick web search, I came across a great post on the Customer Effective blog called Execute Fetch from JavaScript in CRM 2011. That post shows a custom FetchUtil.js library that does three things:
- Create a properly formatted SOAP request for a FetchXML query
- Execute the request using the XMLHttpRequest object
- Return the XML response to the browser
function executeFetchCommand() {
/// <summary>
/// reads user-supplied FetchXML contained in the txtFetch textarea and starts processing
/// </summary>
//generate the SOAP envelope
var fetchRequest = buildFetchRequest($("textarea#txtFetch").val());
//send the request to CRM
sendQuery(fetchRequest);
}
function buildFetchRequest(fetch) {
/// <summary>
/// builds a properly formatted FetchXML request
/// based on Paul Way's blog post "Execute Fetch from JavaScript in CRM 2011"
/// http://blog.customereffective.com/blog/2011/05/execute-fetch-from-javascript-in-crm-2011.html
/// </summary>
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(fetch);
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 request;
}
function sendQuery(fetchRequest) {
/// <summary>
/// uses jQuery ajax method to executes a FetchXML query and register a callback function
/// </summary>
//path to CRM root
var server = window.location.protocol + "//" + window.location.host;
//full path to CRM organization service - you may need to modify this depending on your particular situation
var path = server + "/XRMServices/2011/Organization.svc/web";
$("#outputdiv").append("Starting ajax request . . . <br />");
$.ajax({
type: "POST",
dataType: "xml",
contentType: "text/xml; charset=utf-8",
processData: false,
url: path,
data: fetchRequest,
beforeSend: function( xhr ){
xhr.setRequestHeader( //without the SOAPAction header, CRM will return a 500 error
"SOAPAction",
"http://schemas.microsoft.com/xrm/2011/ Contracts/Services/IOrganizationService/Execute" //remove the extra space inserted to help this line wrap
);
}
}).done(function(data) {
//if successful, pass returned xml and the plot label to the charting function
processData(data);
}).fail(function(jqXHR, textStatus, errorThrown ) {
//if unsuccessful, generate an error alert message
alert( "Request failed: " + textStatus + "\n" + errorThrown );
});
$("#outputdiv").append("Ajax request complete. Output will be processed asynchronously. <br />");
}
- The inside the "done" function is where we tell jQuery what to do when a successful response returns data. We will look at the processData method more closely in the next section.
- The "fail" function generates an alert window with error output. You could do something more sophisticated.
- You probably want to leave all the other ajax configuration options alone unless you have a good reason not to. It took a good bit of trial and error for me to get this right (probably because I was up too late at night working it out, but I digress . . . ).
<fetch distinct='false' mapping='logical' aggregate='true'> <entity name='contact'> <attribute name='fullname' alias='contact_count' aggregate='count' /> <attribute name='createdon' groupby='true' dategrouping='day' alias='day' /> <attribute name='createdon' groupby='true' dategrouping='month' alias='month' /> <attribute name='createdon' groupby='true' dategrouping='year' alias='year' /> </entity> </fetch>
- Each row of data is represented as an "a:Entity" element.
- Individual attribute names and values can be found in the an entity's "a:Attributes" element and also in its "a:FormattedValues" element. In this example I will be working with the "a:FormattedValues" element because it's marginally easier to parse, however you may find times that it makes more sense to work with "a:Attributes" instead.
- Everything under "a:FormattedValues" is represented as a string, so we would need to convert to other datatypes where necessary (ie. building a date object from the returned month / day / year values).
- Each key-value pair under "a:FormattedValues" is contained in an "a:KeyValuePairOfstringstring" element.
function processData(xml) {
/// <summary>
/// writes key-value pairs to a div
/// </summary>
alert("Output started");
$("#outputdiv").append("OUTPUT FROM CRM <br />");
//outer loop
$(xml).find("a\\:Entity").each(function() {
//write entity
$("#outputdiv").append("Entity <br />");
//inner loop
$(this).find("a\\:KeyValuePairOfstringstring").each(function() {
var xmlElement = $(this);
var key = xmlElement.find("b\\:key").text();
var value = xmlElement.find("b\\:value").text();
$("#outputdiv").append(" Key: " + key + " Value: " + value + "<br />");
});
});
alert("Output complete");
}
- In the demo web page, I am using the jQuery library hosted by the Microsoft Ajax Content Delivery Network. If your users are accessing a CRM instance on your LAN, it would be better to host the jQuery library inside of CRM.
- I don't know how this page hosted as a web resource will work in browsers other than Internet Explorer. Initially I did most of the jQuery prototyping work with a locally stored XML file on my PC using Chrome, and that code didn't work in IE without a lot of changes. Particularly noteworthy is that Chrome could find the XML elements without me needing to specify a namespace ("a:" or "b:"), but IE wouldn't. I did not go back and test whether the modified find selectors worked in Chrome.
This was originally posted here.

Like
Report
*This post is locked for comments