Wednesday, July 3, 2013

Making FetchXml in Javascript Less SOAPy and XMLy


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?
  1. Search the internet for “CRM FetchXML from JavaScript
  2. Copy and paste some hard coded SOAP XML from a random post somewhere
  3. Shoehorn your FetchXML into it.
  4. 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.
  5. 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:

IE Debugger Window


Here are a couple things to take note of:
  1. The response returned is just an array of entities.
  2. 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.
  1. getXMLHttpRequest - returns an XMLHttpRequestObject in a browser specific safe manner.
  2. _getServerUrl – returns the URL for the SOAP endpoint using the context information available in the form or HTML Web Resource.
  3. 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:
  1. Search the internet for “CRM FetchXML from JavaScript”
  2. Download this library
  3. Never repeat steps 1-2 again
  4. Use your FetchXML as is, calling executeFetchXmlRequest
  5. Easily access the data you need in the response
  6. Go home early, and sleep better at night*

*Step 6 is not required but highly likely ;)


Click here to download the source code.


4 comments:

  1. Hi,

    I'm having some problems using this code. I get null back when I use the executeFetchXmlRequest function.

    I'm running Windows 8, IE10, CRM 2011 rollup 13.

    Any ideas on this?

    Thanks!

    ReplyDelete
  2. Marten, this was developed and tested on IE 9, CRM rollup 10. I know that IE had some significant changes so I'm guessing their may be a browser issue. Use F12 to debug in IE, and set a break point in the _onFetchXmlSuccess message and see what the response is. If the response looks good, then the next part to check is the _getElementText function to see if that is working correctly. If you find a bug, please let me know.

    ReplyDelete
  3. From what I can tell I actually get a response with the expected records. The problem is that successCallback(req.responseXML.xml);returns null/undefined. req.responseXML is an object though. Any thoughts on this?

    Thanks for your time Daryl

    ReplyDelete
  4. Replaced "responseXML" with "responseText" and it works in IE and FF.

    ReplyDelete