In CRM's form customization, we often need to implement business logics based on the information that is not available right away from the crmForm object. In this case, it's common practice to use CRM Web Service to retrieve this type of information from CRM database. This is what I have been doing extensively in my current CRM project, so I spent some of my weekend time to create a re-usable JavaScript CRM Web Service Helper, which you can copy and use.
CrmServiceHelper = function() { /** * CrmServiceHelper 1.0 * * @author Daniel Cai * @website http://danielcai.blogspot.com/ * @copyright Daniel Cai * @license Microsoft Public License (Ms-PL), http://www.opensource.org/licenses/ms-pl.html * * This release is provided "AS IS" and contains no warranty or whatsoever. * * Date: Sep 27 2009 */ // Private members var DoRequest = function(soapBody, requestType) { //Wrap the Soap Body in a soap:Envelope. var soapXml = "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' " + "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " + "xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" + GenerateAuthenticationHeader() + "<soap:Body><" + requestType + " xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>" + soapBody + "</" + requestType + ">" + "</soap:Body>" + "</soap:Envelope>"; var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); xmlhttp.open("POST", "/MSCRMServices/2007/crmservice.asmx", false); xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/" + requestType); //Send the XMLHTTP object. xmlhttp.send(soapXml); var resultXml = xmlhttp.responseXML; if (resultXml === null || resultXml.xml === null || resultXml.xml === "") { if (xmlhttp.responseText !== null && xmlhttp.responseText !== "") { throw new Error(xmlhttp.responseText); } else { throw new Error("No response received from the server. "); } } // Report the error if occurred var error = resultXml.selectSingleNode("//error"); var faultString = resultXml.selectSingleNode("//faultstring"); if (error !== null || faultString !== null) { throw new Error(error !== null ? resultXml.selectSingleNode('//description').nodeTypedValue : faultString.text); } return resultXml; }; var BusinessEntity = function(sName) { this.name = sName; this.attributes = new Object(); }; var DataType = { String : "string", Boolean : "boolean", Int : "int", Float : "float", DateTime : "datetime" }; // Public members return { BusinessEntity : BusinessEntity, DataType : DataType, DoRequest : DoRequest, Retrieve : function(entityName, id, columns) { var attributes = ""; if (typeof attributes !== "undefined") { for (var i = 0; i < columns.length; i++) { attributes += "<q1:Attribute>" + columns[i] + "</q1:Attribute>"; } } var msgBody = "<entityName>" + entityName + "</entityName>" + "<id>" + id + "</id>" + "<columnSet xmlns:q1='http://schemas.microsoft.com/crm/2006/Query' xsi:type='q1:ColumnSet'>" + "<q1:Attributes>" + attributes + "</q1:Attributes>" + "</columnSet>"; var resultXml = DoRequest(msgBody, "Retrieve"); var xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async = false; xmlDoc.loadXML(resultXml.xml); var retrieveResult = xmlDoc.selectSingleNode("//RetrieveResult"); if (retrieveResult === null) { throw new Error("Invalid result returned from server. "); } var resultNodes = retrieveResult.childNodes; var returnEntity = new BusinessEntity(); for (var i = 0; i < resultNodes.length; i++) { var fieldNode = resultNodes[i]; var field = {}; field["value"] = fieldNode.text; for (var j = 0; j < fieldNode.attributes.length; j++) { field[fieldNode.attributes[j].nodeName] = fieldNode.attributes[j].nodeValue; } returnEntity.attributes[fieldNode.baseName] = field; } return returnEntity; }, Fetch : function(xml) { var msgBody = "<fetchXml>" + _HtmlEncode(xml) + "</fetchXml>"; var resultXml = DoRequest(msgBody, "Fetch"); var xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async = false; xmlDoc.loadXML(resultXml.xml); var fetchResult = xmlDoc.selectSingleNode("//FetchResult"); if (fetchResult === null) { throw new Error("Invalid result returned from server. "); } xmlDoc.loadXML(fetchResult.childNodes[0].nodeValue); var resultNodes = xmlDoc.selectNodes("/resultset/result"); var results = []; for (var i = 0; i < resultNodes.length; i++) { var resultEntity = new BusinessEntity(); for (var j = 0; j < resultNodes[i].childNodes.length; j++) { var fieldNode = resultNodes[i].childNodes[j]; var field = {}; field["value"] = fieldNode.text; for (var k = 0; k < fieldNode.attributes.length; k++) { field[fieldNode.attributes[k].nodeName] = fieldNode.attributes[k].nodeValue; } resultEntity.attributes[fieldNode.baseName] = field; } results[i] = resultEntity; } return results; }, Execute : function(request) { var msgBody = request; var resultXml = DoRequest(msgBody, "Execute"); var xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async = false; xmlDoc.loadXML(resultXml.xml); return xmlDoc; }, ParseValue : function(businessEntity, crmProperty, type, crmPropertyAttribute) { if (businessEntity === null || typeof crmProperty === "undefined" || !businessEntity.attributes.hasOwnProperty(crmProperty)) { return null; } var value = (typeof crmPropertyAttribute !== "undefined") ? businessEntity.attributes[crmProperty][crmPropertyAttribute] : businessEntity.attributes[crmProperty].value; switch (type) { case DataType.Boolean: return (value !== null) ? (value === "1") : false; case DataType.Float: return (value !== null) ? parseFloat(value) : 0; case DataType.Int: return (value !== null) ? parseInt(value) : 0; case DataType.DateTime: return (value !== null) ? ParseDate(value) : null; case DataType.String: return (value !== null) ? value : ""; default: return (value !== null) ? value : null; } return null; } }; }();
- Fetch a list of records or one record, in which case you can use CrmServiceHelper.DoFetchXmlRequest().
var fetchXml = '<fetch mapping="logical">' + '<entity name="account">' + '<attribute name="name" />' + '<attribute name="primarycontactid" />' + '<filter>' + '<condition attribute="accountid" operator="eq" value="' + crmForm.all.accountid.DataValue[0].id + '" />' + '</filter>' + '</entity>' + '</fetch>'; var fetchResult = CrmServiceHelper.Fetch(fetchXml); alert(CrmServiceHelper.ParseValue(fetchResult[0], 'name'));
- Retrieve one record.
var retrieveResult = CrmServiceHelper.Retrieve('account', crmForm.all.accountid.DataValue[0].id, ['accountid', 'name']); alert(CrmServiceHelper.ParseValue(retrieveResult, 'name'));
- Execute a request.
function GetCurrentUserId() { var request = "<Request xsi:type='WhoAmIRequest' />"; var xmlDoc = CrmServiceHelper.Execute(request); var userid = xmlDoc.getElementsByTagName("UserId")[0].childNodes[0].nodeValue; return userid; }
A few final notes about Web Service Helper:
- CrmServiceHelper is designed as a container object to provide all necessary interfaces to interact with CRM Web Service through JavaScript. By this approach, we don't pollute JavaScript global namespace with a lot of objects.
- All functions in CrmServiceHelper throws an error when exception happens, it's your responsibility to handle this type of exception. The common practice is using try/catch block, but it's totally up to you. If you don't use try/catch block, CRM platform will catch it, and give the user an alert warning window.
- You should probably consider saving the above script to a file such as CrmServiceHelper.js, and upload the file to your CRM's ISV folder, then use Henry Cordes' load_script() function to load and consume the service helper in the form's onload event.
- When you use CrmServiceHelper.GetValueFromFetchResult() function to parse the fetched result, please make sure to specify the datatype correctly.
- FetchXML is extremely flexible, it can do almost anything that you may want (except some native SQL functions such as SOUNDEX, etc.) in CRM, FetchXML is usually my first choice when I need to retrieve more than one record from CRM using JavaScript (In C#, you might want to use Query Expression due to the syntax friendship in IDE environment). You might want to consider using Stunnware Tools to help you create FetchXML in a more productive way.
[UPDATE - Jan 21, 2010] I have released an updated version of script under the name of CRM Web Service Toolkit, please check my latest blog for the details.