Monday, January 25, 2010

Fiddler to Help Debug an IE Dialog Page

This maybe a well-known tip for web developers, but I was not aware of it until recently I had a need to debug a dialog page of Microsoft Dynamics CRM, so I am trying to document it, which might help you if you ever need to do the same thing.

We all know that IE 8 has provided some great development features including script debugging, DOM inspecting, document validating, etc. All those features can be brought up by simply pressing F12 key. However, this hotkey DOES NOT work for a dialog page which is invoked by IE's proprietary function - window.showModalDialog().

I was pulling my hair out desperately the other day when I was in a urgent need to debug a CRM dialog page. I did ask my Google friend (or Bing if you prefer that way) at first thought, but I got nothing really useful.

Then I thought, wait a second, should I check if my favorite debugging tool – Fiddler is able to help? I am pleased that I have thought of it, though not in the first place.

Here are the steps that you can follow to debug a IE dialog page.
  1. Launch Fiddler, and make sure Capture Traffic option is selected. (It’s selected by default)
  2. Launch your IE browser, do whatever you need to do in order to bring up the dialog page.
  3. Go back to Fiddler, find the session of the dialog page, and right click on it (shown in the following screen shot). Then you select Copy –> Just Url menu option.
    Fiddler
  4. Launch a new IE session, paste the URL to the address bar, and press Enter key. You should know what to do next, all you need to do is to simply press F12 key, then you will be able to see the IE development tool, where you can do whatever you want to debug the dialog page.
    IEDialogDebugging
Small trick, hope it helps. BTW, it works for any IE dialog page.

[Update - Apr 11, 2010] I have discovered, there is an even more efficient way, you can simply click "Launch IE" on the toolbar after you have located the HTTP request session. This really proves that Fiddler is a great HTTP analysis tool with extraordinary features that we are not even fully aware.

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.