Thursday, January 21, 2010

MSCRM 4.0 Web Service Toolkit (JavaScript)

[Update - May 23, 2010] There has been a new version released (v2.0), which you might want to check out.

Today, I managed to get an updated CRM Web Service Toolkit (Formerly CRM Web Service Helper) released to codeplex site. This is a major update from the previous helper utility, with the following enhancements:

  1. The toolkit now supports all important CRM Web Service messages, including Create, Update, Delete, Fetch, Retrieve, RetrieveMultiple, Execute.
  2. The toolkit tries to automatically determine the data type returned from CRM Web Service, when retrieving messages (Fetch, Retrieve, RetrieveMultiple) are used. The only exception is CRM bit type, which I cannot differentiate from a numeric type returned by CRM Web Service. For CRM bit type, you will need to convert to boolean using the Number's toBoolean() prototype function (aka instance function), which is provided in the toolkit.
  3. All request string / response strings are now properly encoded or decoded.

[CREDITS] The idea behind CrmServiceToolkit.BusinessEntity was inspired by Ascentium CrmService JavaScript Library, after I have finished most of the code. Hats off to Ascentium CRM practice team.

In order to help you get started with the toolkit, the following are a few samples that might give you some hints about how to use the toolkit in your form script.

  1. Use CrmServiceToolkit.Create() to create a CRM record.
    // Use CrmServiceToolkit. Create() to create a CRM contact record.
    var contact = new CrmServiceToolkit.BusinessEntity("contact");
    contact.attributes["firstname"] = "Diane";
    contact.attributes["lastname"] = "Morgan";
    contact.attributes["gendercode"] = 2;
    contact.attributes["familystatuscode"] = 1; // Picklist : Single - 1
    contact.attributes["creditlimit"] = 3000;
    
    var createResponse = CrmServiceToolkit.Create(contact);
  2. Use CrmServiceToolkit.Update() to update a CRM record.
    //Use CrmServiceToolkit.Update() to update a CRM contact record. 
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C';
    var contact = new CrmServiceToolkit.BusinessEntity("contact");
    contact.attributes["contactid"] = contactId;
    contact.attributes["firstname"] = "Diane";
    contact.attributes["lastname"] = "Lopez";
    contact.attributes["familystatuscode"] = 2; // Married
    
    var updateResponse = CrmServiceToolkit.Update(contact);   
  3. Use CrmServiceToolkit.Retrieve() to retrieve a CRM record.
    // Use CrmServiceToolkit.Retrieve() to retrieve a CRM contact record.
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C'; 
    var cols = ["firstname", "lastname", "familystatuscode", "creditlimit", "birthdate", "donotemail"];
    var retrievedContact = CrmServiceToolkit.Retrieve("contact", contactId, cols);
    
    alert(retrievedContact.getValue('lastname'));
    alert(retrievedContact.getValue('firstname'));
    alert(retrievedContact.getValue('familystatuscode')); // Picklist's value (integer)
    alert(retrievedContact.getValue('familystatuscode', 'name')); // Picklist's selected text
    alert(retrievedContact.getValue('creditlimit')); // Currency field's value
    alert(retrievedContact.getValue('creditlimit', 'formattedvalue')); // Currency field's formatted value (string)
    alert(retrievedContact.getValue('birthdate')); // Datetime field's date/time value
    alert(retrievedContact.getValue('birthdate', 'date')); // Datetime field's date string
    alert(retrievedContact.getValue('birthdate', 'time')); // Datetime field's time string
    alert(retrievedContact.getValue('donotemail').toBoolean()); // Bit field's value
  4. Use CrmServiceToolkit.RetrieveMultiple() to retrieve a collection of CRM records.
    // Retrieve all contacts whose first name is John. 
    var firstname = 'John'; 
    var query =
    "<q1:EntityName>contact</q1:EntityName>" +
    "<q1:ColumnSet xsi:type='q1:ColumnSet'>" +
       "<q1:Attributes>" +
          "<q1:Attribute>firstname</q1:Attribute>" +
          "<q1:Attribute>lastname</q1:Attribute>" +
          "<q1:Attribute>familystatuscode</q1:Attribute>" +
          "<q1:Attribute>ownerid</q1:Attribute>" +
          "<q1:Attribute>creditlimit</q1:Attribute>" +
          "<q1:Attribute>birthdate</q1:Attribute>" +
          "<q1:Attribute>donotemail</q1:Attribute>" +
       "</q1:Attributes>" +
    "</q1:ColumnSet>" +
    "<q1:Distinct>false</q1:Distinct>" +
    "<q1:Criteria>" +
       "<q1:FilterOperator>And</q1:FilterOperator>" +
       "<q1:Conditions>" +
          "<q1:Condition>" +
             "<q1:AttributeName>firstname</q1:AttributeName>" +
             "<q1:Operator>Equal</q1:Operator>" +
             "<q1:Values>" +
                "<q1:Value xsi:type='xsd:string'>" + firstname + "</q1:Value>" +
             "</q1:Values>" +
          "</q1:Condition>" +
       "</q1:Conditions>" +
    "</q1:Criteria>";
    
    var retrievedContacts = CrmServiceToolkit.RetrieveMultiple(query);
    
    alert(retrievedContacts.length);
    alert(retrievedContacts[0].getValue('lastname'));
    alert(retrievedContacts[0].getValue('firstname'));
    alert(retrievedContacts[0].getValue('familystatuscode');
    alert(retrievedContacts[0].getValue('familystatuscode', 'name'));
    alert(retrievedContacts[0].getValue('creditlimit'));
    alert(retrievedContacts[0].getValue('creditlimit', 'formattedvalue'));
    alert(retrievedContacts[0].getValue('birthdate'));
    alert(retrievedContacts[0].getValue('birthdate', 'date'));
    alert(retrievedContacts[0].getValue('birthdate', 'time'));
    alert(retrievedContacts[0].getValue('donotemail').toBoolean());
  5. Use CrmServiceToolkit.Fetch() to retrieve a collection of CRM records using FetchXML query.
    // Fetch all contact records whose first name is John using FetchXML query
    var firstname = 'John';
    var fetchXml =
    "<fetch mapping='logical'>" +
       "<entity name='contact'>" +
          "<attribute name='contactid' />" +
          "<attribute name='firstname' />" +
          "<attribute name='lastname' />" +
          "<attribute name='familystatuscode' />" +
          "<attribute name='ownerid' />" +
          "<attribute name='creditlimit' />" +
          "<attribute name='birthdate' />" +
          "<attribute name='accountrolecode' />" +
          "<attribute name='donotemail' />" +
          "<filter>" +
             "<condition attribute='firstname' operator='eq' value='" + firstname + "' />" +
          "</filter>" +
       "</entity>" +
    "</fetch>";
    
    var fetchedContacts = CrmServiceToolkit.Fetch(fetchXml);
    
    alert(fetchedContacts.length);
    alert(fetchedContacts[0].getValue('lastname'));
    alert(fetchedContacts[0].getValue('firstname'));
    alert(fetchedContacts[0].getValue('familystatuscode');
    alert(fetchedContacts[0].getValue('familystatuscode', 'name'));
    alert(fetchedContacts[0].getValue('creditlimit'));
    alert(fetchedContacts[0].getValue('creditlimit', 'formattedvalue'));
    alert(fetchedContacts[0].getValue('birthdate'));
    alert(fetchedContacts[0].getValue('birthdate', 'date'));
    alert(fetchedContacts[0].getValue('birthdate', 'time'));
    alert(fetchedContacts[0].getValue('donotemail').toBoolean());
  6. Use CrmServiceToolkit.Delete() to delete a CRM record.
    // Use CrmServiceToolkit.Delete() to delete a CRM contact record. 
    var contactId = '3210F2BC-1630-EB11-8AB1-0003AAA0123C';
    var deleteResponse = CrmServiceToolkit.Delete("contact", contactId);
    alert(deleteResponse);
  7. Use CrmServiceToolkit.Execute() to execute a message.
    // Use CrmServiceToolkit.Execute() to execute a message. 
    var whoAmI = CrmServiceToolkit.Execute("<Request xsi:type='WhoAmIRequest' />");
    currentUserId = whoAmI.getElementsByTagName("UserId")[0].childNodes[0].nodeValue;
    alert("Current user's ID is ' + currentUserId);

    I have to point out, CrmServiceToolkit.Execute() method can do a lot more than just WhoAmIRequest. Please refer to MSCRM 4.0 SDK for more details.

A few more notes about the toolkit.
  1. The following CRM JavaScript functions have been used in order to keep the file size minimal (Aside from this reason, I am not a big fan of reinventing the wheel).
    • GenerateAuthenticationHeader() function
    • _HtmlEncode() function
    • CrmEncodeDecode.CrmXmlDecode() function
    • CrmEncodeDecode.CrmXmlEecode() function

    If you ever need to run the toolkit out of the CRM form, you will need to make the above functions available to the toolkit script.

  2. When you retrieve records from CRM using the toolkit's Fetch, Retrieve, RetrieveMultiple methods, what you get will be the instance(s) of CrmServiceToolkit.BusinessEntity, which contains all CRM attributes (fields) that have been returned from CRM. However, you should not try to access those attributes directly, instead you use the instance function - getValue() to get the value of the CRM field. The reason behind this is, CRM doesn't return anything if a field's value is null, in which case your JS code will blow up if you try to access the field (attribute) directly. With that being said, you should also be informed that a CRM filed's value could be null, be sure to handle it properly in your JS code.
  3. As mentioned previously, be careful when dealing with CRM bit data type if you are retrieving CRM records from CRM (Only Fetch, Retrieve, RetrieveMultiple methods are really concerned). The value you get from entity’s getValue() function is actually a number, which in most case will satisfy your needs. In case you need to assign the retrieved value to another CRM bit field, you should use toBoolean() function to convert to actual JavaScript bool type, as I have shown in the above sample.
  4. CRM's Execute message is a versatile message. Anything that you cannot easily achieve through the other 6 messages, you should resort to the toolkit’s Execute() method. Again, please refer to MSCRM 4.0 SDK for more CRM messages.
  5. The toolkit release has a test page included (CrmServiceToolkitTest.aspx), which utilizes QUnit as the test engine. In order to run the test script, you should deploy it along with all other files to ISV/CrmServiceToolkit folder (Please create this folder first), then you can launch http://crmserver:port/YourOrgName/CrmServiceToolkit/CrmServiceToolkitTest.aspx to run it. If you are in good luck, you should see a screen like this:

    CrmWebServiceToolkitTest

I hope that I have covered everything.

Have fun with the toolkit, hope it can make your CRM programming more enjoyable.

[Update - Apr 2, 2010] I started to poke around the toolkit by implementing it in the project at my work recently (I was still using the Helper one) and realized a bug with regard to parsing CRM boolean field when its value is false. I have updated the download package at codeplex site, please download the latest one. I am very sorry for the inconvenience, I should have been the first user of my toolkit. ;)

[Update - May 23, 2010] There has been a new version released (v2.0), which you might want to check out.

32 comments:

  1. Looks very promising. I think i am going to enjoy this!

    ReplyDelete
  2. I agree, thanks.
    Let's have fun.

    ReplyDelete
  3. Nice, clean and simple. Well done and thanks for Sharing. Have you seen David Yacks, XRM as a development platform, it has a similar js web-service framework/toolkit, it has some classes for the retrieve multiple and a nice IsInRole(sRoleName) method, worth a look if you get a chance.

    ReplyDelete
  4. Thanks everyone for the positive feedback.

    Simon, I haven't got a chance to read David's book, which I really look forward to. If I ever knew that there was something similar, I may not be motivated to write my own version. :) Thanks for sharing the information with me.

    ReplyDelete
  5. Hi Daniel,

    I pasted the CrmServiceToolkit folder inside ISV folder. But when i paste the code CrmServiceToolkit.Fetch(fetchXml);in my onload i get the error CrmServiceToolkit is undefined. Hav i made any mistake in pasting the folder in the right location?

    ReplyDelete
  6. Hi Daniel,
    I managed to run this :-) thanks for your wonderful codes

    ReplyDelete
  7. Thanks aarch.

    In case you haven't noticed, I have release a new version at http://crmtoolkit.codeplex.com.

    BTW, I have removed an advertisement comment that was posted a few hours ago. As I have mentioned in the disclaimer, I do not tolerate any SPAM. Please don't bother trying.

    Cheer,
    Daniel

    ReplyDelete
  8. Daniel Cai/aarch,
    I have the same problem, i put the code into OnChange event and i get the error CrmServiceToolkit is undefined. I need you help!

    ReplyDelete
  9. edudebolivar,

    Did you actually copy the content of CrmServiceToolkit.js file to your form's OnLoad event?

    Alternatively, if you don't want to copy the entire toolkit to your form's OnLoad event, you could check my another blog post for the script that you may use to load from external js file.

    BTW, there is a new release at http://crmtoolkit.codeplex.com, you might want to check out.

    Cheers,
    Daniel

    ReplyDelete
  10. Daniel,

    Thank you very much,
    you don´t know how this post was useful for me!

    ReplyDelete
  11. Hi,
    I am using same Update code but getting an error Message while Update a record:

    ---------------------------
    Message from webpage
    ---------------------------
    There was an error with this field's customized event.

    Field:crmForm

    Event:onsave

    Error:Public assembly must have public key token.
    ---------------------------
    OK
    ---------------------------

    I am not understanding why I am getting this error. Please assist me.

    ReplyDelete
  12. Hi Abhay,

    Sorry for the late reply. The problem that you are having could be caused by a number of reasons. You will need to use some JavaScript debugging techniques to find out what is actually wrong. It could be an exception received by the toolkit because of server exception, but it could also be something else.

    Again, sorry for the late response.

    Thanks,
    Daniel

    ReplyDelete
  13. Hi Daniel,

    I put all the contents on ISV folder, but i dind't passed the test, cause:

    "1.Died on test #1: 'StringBuilder' it's not defined"

    I need your help.
    I'm using CRM4.0.

    Thanks in advance.

    ReplyDelete
  14. Hi Mayte,

    That's a strange error. What browser do you use? IE6? Do you load a CRM form fine? There is no reference to StringBuilder in the toolkit, neither is it used in qunit.js test framework, so I guess it might come from CRM script, but I am not so sure.

    -Daniel

    ReplyDelete
  15. Hi Daniel,

    i was trying to create a new customeraddress record and while creating it using the Create method i am getting an error which says "An unspecified error occured.".
    The way i am creating the record is:
    var address = new CrmServiceToolkit.BusinessEntity("customeraddress");
    address.attributes["name"] = "Address1";
    address.attributes["addresstypecode"] = 1;
    address.attributes["objecttypecode"] = 2;
    address.attributes["line1"] = "Test Address Creation";
    address.attributes["parentid"] = 'A67D74C6-7ACA-E011-82AF-4A9C2A321208';
    var createResponse = CrmServiceToolkit.Create(address);
    Could you please let me know what is it that i am doing wrong here..

    Thanks,
    Suhani

    ReplyDelete
  16. Hi Suhani,

    I believe the problem is with parentid field. It's a lookup field, so you should provide its typename. The following modification should help you address the problem.

    address.attributes["parentid"] = {typename: 'account', id: 'A67D74C6-7ACA-E011-82AF-4A9C2A321208'};

    I assume the provided id is for an account, but it could be contact in your case, which you should know.

    If you still have problem, you should turn on CRM platform trace log.

    Hope this helps.

    Cheers,
    Daniel

    ReplyDelete
  17. Hey Daniel.

    I am trying to create a phonecall ativity using the method CrmServiceToolkit.Create

    Can you please advise how i could assign value to "from" and "to" attribute in the code.

    I have tried something like
    var phonecall = new CrmServiceToolkit.BusinessEntity("phonecall");
    phonecall.attributes["from"] = { $value: senderId, type: "systemuser" };
    phonecall.attributes["to"] = { $value: ownerId, type: "systemuser" };
    phonecall.attributes["regardingobjectid"] = { $value: accountId, type: "account" };

    However the code does not work for from and to, but does work for regardingobjectid.

    Is there anything wrong here? The values of senderId, and ownerId both are correct.

    Jaimie

    ReplyDelete
  18. @Jaimie, please check your email. I have sent you a private build for this issue.

    For anyone else who is interested in the private build, please feel free to request.

    The problem is, activityparty is special type of field in Microsoft Dynamics CRM, which requires some special handling in order to get it correct. I wasn't aware of this at the time when I developed the toolkit, since I didn't get much exposure to CRM activity entities back then.

    ReplyDelete
  19. @Daniel Cai. Thanks very much for the private build.

    Any question i have is if the toolkit is working for CRM 2011? Can you explain the differences between CRM 4 and CRM 2011 Soap per JavaScript usage.

    Thanks,
    Jaimie

    ReplyDelete
  20. @Jaimie, the toolkit works for CRM2011, since the SOAP 2007 endpoint has not been changed. However using SOAP 2007 endpoint in CRM 2011 is not a recommended practice, since SOAP 2007 is provided for backwards compatibility purpose, which might be deprecated in future version.

    ReplyDelete
  21. Hi Daniel,
    I have used this utility on CRM 4.0 and CRM 2011 and it works very well with both versions. One thing I want to ask is that will it work with online versions. If so do we require any thing extra?
    Regards
    Faisal

    ReplyDelete
    Replies
    1. Hi Atif,

      I don't have a CRM online instance to verify. What error are you experiencing?

      Thanks,
      Daniel

      Delete
  22. I have retrieved lead regarding phone-call activity i want to set the value of field phone-activity-count on Lead but it give me error "unable to get value of the property 'setValue':object is null or undefined"

    ReplyDelete
  23. here is my code
    function setActivityCount(){
    var lookupItem = new Array();
    lookupValue = Xrm.Page.getAttribute("regardingobjectid").getValue();

    if (lookupValue != null)
    {
    var lname = lookupValue[0].name;
    var lguid = lookupValue[0].id;
    var lentType = lookupValue[0].entityType;
    }

    if(lentType=="lead")
    {
    var leadguid = lguid;
    var columns = ["new_phoneactivitycount", "new_emailactivitycount"];
    var retrievedLead = CrmServiceToolkit.Retrieve("lead", leadguid, columns);


    var value=retrievedLead.getValue('new_phoneactivitycount');
    if(value==null)
    {
    value++;
    Xrm.Page.getAttribute("new_phoneactivitycount").setValue(value);
    }

    ReplyDelete
    Replies
    1. @umair, could it be possible that new_phoneactivitycount is not a field on the form?

      In the code block of "if (value == null)", value++ would fail, it is strange that you didn't receive an error on that line.

      If you are using CRM2011, you should consider moving to the new XrmSvcToolkit which is available at http://xrmsvctoolkit.codeplex.com/

      Hope this help.

      Delete
  24. Hi Daniel,
    I need to update the statecode in an incident from other entity. I'm using this code:

    var attClienteUpdateCerrar = new CrmServiceToolkit.BusinessEntity("incident");
    attClienteUpdateCerrar.attributes["incidentid"] = crmForm.all.new_atencionalclienteid.DataValue[0].id;
    attClienteUpdateCerrar.attributes["statecode"] = '1';

    var updateResponse = CrmServiceToolkit.Update(attClienteUpdateCerrar);

    But it doesn't work. Do you know how to do this?
    Thank you,
    Jose

    ReplyDelete
    Replies
    1. @Jose, you would have to use CrmServiceToolkit.SetState method.

      Delete
  25. Hey guys,,
    Dealer's Choice provides various services to the automotive industry that are designed to make the business process more efficient and streamlinedService ContractsOur mission is to reduce the cost of operations for dealerships and increase their productivity with tools that give customer satisfaction a new meaning.
    Thanks ....!!

    ReplyDelete
  26. This comment has been removed by the author.

    ReplyDelete
  27. This comment has been removed by the author.

    ReplyDelete
  28. I used link-entity in my Xml. The Fetch() function is returning nothing for the inputXml. why so?
    It is working fine for single entity.
    I am searching for this from last 2 days. Please help.

    ReplyDelete