Whenever I have to request data from CRM within Javascript, my first attempt is always to use an OData call. The request urls aren’t too difficult to create (On a side note, there are multiple custom tools for this purpose. I prefer
LinqPad.) and the data transferred in the request and the response is extremely tiny. But… it
has some limitations, especially within CRM. So what do you do when you’ve hit one of the limitations?
- Search the internet for “CRM FetchXML from JavaScript”
- Copy and paste some hard coded SOAP XML from a random post somewhere
- Shoehorn your FetchXML into it.
- Spend an hour parsing the returned XML with the XML Parser, trying to figure out how many .childNode[i] and .firstChild properties you need to string together to get your actual data.
- Feel sorry for the next developer that has to tweak your “ParseResults” code.
Need to do it again 3 months later? Repeat the process form the top. It’s one of those processes that makes you amazed there isn’t an easier way. Well… now there is.
CRM FetchXML Soap Library
In the spirit of “
All problems in computer science being solvable by another level of abstraction,” I’ve create a small JavaScript library to abstract away all of the SOAPy, XMLy, uglyness. Here is an example of it’s use:
// Fetch Xml Call to get all opportunities for TX, with their estimated values
var fetchXml = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">' +
' <entity name="opportunity">' +
' <attribute name="estimatedvalue" />' +
' <order attribute="name" descending="false" />' +
' <link-entity name="account" from="accountid" to="customerid" alias="ae">' +
' <filter type="and">' +
' <condition attribute="address1_stateorprovince" operator="eq" value="TX" />' +
' </filter>' +
' </link-entity>' +
' <link-entity name="account" from="accountid" to="parentaccountid" visible="false" link-type="outer" alias="Account">' +
' <attribute name="address1_stateorprovince" />' +
' </link-entity>' +
' </entity>' +
'</fetch>';
var response = Allegient.Core.SoapLib.executeFetchXmlRequest(fetchXml);
Just give the Library FetchXml, and it’ll give you back a collection of javascript objects that are parsed from the result. Here is what the result looks like in the IE debugger:
Here are a couple things to take note of:
- The response returned is just an array of entities.
- Each entity has a property that maps to a property of the entity returned. This allows working with the entities in a way that is much easier than XML parsing. ie to get the first entity’s name: “response[0].name” or to get the the 2nd entity’s currency’s name: “response[1].transactioncurrencyid.Name”.
The Implementation
There is a lot going on under the covers, so we’ll start at the top with
executeFetchXmlRequest and work our way down:
executeFetchXmlRequest: function (fetchXml, onSuccess, onError, async) {
if (async == null) {
async = false;
}
var request = '<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>' + CrmEncodeDecode.CrmXmlEncode(fetchXml)
+ ' </b:Query>'
+ ' </c:value>'
+ ' </b:KeyValuePairOfstringanyType>'
+ ' </b:Parameters>'
+ ' <b:RequestId i:nil="true"/>'
+ ' <b:RequestName>RetrieveMultiple</b:RequestName>'
+ '</request>';
var response = null;
var internalOnSuccess;
if (onSuccess == null) {
internalOnSuccess = function (r) { response = Allegient.Core.SoapLib._onFetchXmlSuccess(r); };
} else {
internalOnSuccess = function (r) {
var temp = Allegient.Core.SoapLib._onFetchXmlSuccess(r);
response = onSuccess(temp);
if (response == null) {
response = temp;
}
};
}
Allegient.Core.SoapLib.executeRequest(request, internalOnSuccess, onError, async);
return response;
},
executeFetchXmlRequest just wraps the FetchXml with the RetrieveMultipleRequest request xml, encoding the FetchXml as well. Then it sets up some default response handlers, allowing for the request to be made asynchronously. The next step is the
executeRequest:
executeRequest: function (requestXml, onSuccess, onError, async) {
if (async == null) {
async = false;
}
var soapEnvelope = ""
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<s:Body>"
+ " <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">"
+ requestXml
+ " </Execute>"
+ " </s:Body>"
+ "</s:Envelope>";
var req = Allegient.Core.SoapLib.getXMLHttpRequest();
req.open("POST", Allegient.Core.SoapLib._getServerUrl(), async)
// Responses will return XML. It isn't possible to return JSON.
req.setRequestHeader("Accept", "application/xml, text/xml, */*");
req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
req.onreadystatechange = function () { Allegient.Core.SoapLib.ExecuteSOAPResponse(req, onSuccess, onError); };
req.send(soapEnvelope);
},
It’s primary purpose is to wrap the CRM Request Xml in a Soap Envelope. It can be used to wrap any SOAP calls to CRM to help keep them DRY (Included in the library is an ExecuteWorkflowRequest to illustrate how another request type would utilize
executeRequest).
It utilizes three helper functions.
- getXMLHttpRequest - returns an XMLHttpRequestObject in a browser specific safe manner.
- _getServerUrl – returns the URL for the SOAP endpoint using the context information available in the form or HTML Web Resource.
- ExecuteSOAPResponse – Handles the callback from the request, determining based on the readyState and status if it executed successfully or with an error, calling the appropriate call back.
The default error callback just displays the error in an alert(). The default success callback as defined by
executeFetchXmlRequest is _
onFetchXmlSuccess:
_onFetchXmlSuccess: function (response) {
response = Allegient.Core.SoapLib._replaceAll(response, "<ExecuteResponse ", "<a:ExecuteResponse ");
response = Allegient.Core.SoapLib._replaceAll(response, "<ExecuteResult ", "<a:ExecuteResult ");
response = response.replace(/<(\/?)([^:>]*:)?([^>]+)>/g, "<$1$3>");
var doc = Allegient.Core.SoapLib._parseXmlString(response);
if (Allegient.Core.SoapLib._getElementText(doc.getElementsByTagName("ResponseName")[0]) == "RetrieveMultiple") {
var elements = doc.getElementsByTagName("Entity");
var entities = new Array();
for (var i = 0; i < elements.length; i++) {
entities[i] = Allegient.Core.SoapLib.XML2jsobj(elements[i]);
}
return entities;
} else {
return doc.getElementsByTagName("Results")[0];
}
},
There is some RegEx magic to remove the namespace prefixes from the XML so XML2jsobj returns valid property names.
Small side note here, the ExecuteResponse and ExecuteResult are the only xml elements in the response that don’t have a namesapce and my current regex doesn’t handle them correctly, hence the manual addition of the namespace prefix to the tag. _parseXmlString returns a valid XMLDomParser that has loaded the xml passed in. A quick walk of the XML Dom to see if this is a RetrieveMultiple Response, and if so, each entity is parsed one at a time by
XML2jobj. If it’s not a RetrieveMultiple response, the DOM results from the response are returned.
XML2jobj is based off of a version by
Craig Buckler, but specifically modified for reading CRM attribute data:
XML2jsobj: function (node) {
var data = {};
var isNull = true;
// append a value
function Add(name, value) {
isNull = false;
if (data[name]) {
if (data[name].constructor != Array) {
data[name] = [data[name]];
}
data[name][data[name].length] = value;
}
else {
data[name] = value;
}
};
function AddAttributes(entity, node) {
for (var i = 0; i < node.childNodes.length; i++) {
isNull = false;
//KeyValuePairOfstringanyType
var valuePair = node.childNodes[i];
var key = Allegient.Core.SoapLib._getElementText(valuePair.firstChild);
var value = valuePair.childNodes[1].childNodes;
if (value.length == 1 && value[0].tagName == "Value") {
data[key] = Allegient.Core.SoapLib._getElementText(value[0]);
} else if (value.length == 1 && value[0].nodeType == 3) {
data[key] = value[0].nodeValue;
} else {
data[key] = Allegient.Core.SoapLib.XML2jsobj(valuePair.childNodes[1]);
}
}
};
var c, cn;
// child elements
for (c = 0; cn = node.childNodes[c]; c++) {
if (cn.nodeType == 1) {
if (cn.childNodes.length == 1 && cn.firstChild.nodeType == 3) {
// text value
Add(cn.nodeName, cn.firstChild.nodeValue);
}
else if (cn.nodeName == "Attributes") {
AddAttributes(data, cn)
} else {
// sub-object
Add(cn.nodeName, Allegient.Core.SoapLib.XML2jsobj(cn));
}
}
}
if (isNull) {
data = null;
}
return data;
},
It loops through the XML DOM, creating a new js object for each node in the xml, with special processing for the attributes collection, resulting in the response looking like the screen shot above.
Conclusion
The CRM FetchXML SOAP Library is extremely helpful, allowing for just the FetchXML to be passed in (no need to worry about SOAP headers or the wrapping Request XML itself) and a collection of entity JavaScript objects returned. It should now change your javascript FetchXML steps from what is listed at the top of this article, to this:
- Search the internet for “CRM FetchXML from JavaScript”
- Download this library
- Never repeat steps 1-2 again
- Use your FetchXML as is, calling executeFetchXmlRequest
- Easily access the data you need in the response
- Go home early, and sleep better at night*
*Step 6 is not required but highly likely ;)
Click
here to download the source code.