Share via


Microsoft Dynamics CRM 2011 Jscript Soap Request Formatter

Originally taken from: http://mileyja.blogspot.com/2011/03/crm-2011-jscript-soap-request-formatter.html

I know some of you have seen the crazy Jscript libraries I have been putting up here recently. What if you could take the XML from a captured request and create one of those things instantaniously?  Well, now you can with my CRM 2011 Jscript SOAP Request Formatter, hosted on CodePlex, it takes much of the tediousness out of formatting straight XML for Jscript SOAP calls within CRM 2011.

Link to CodePlex project: http://crm2011soap.codeplex.com/

 

Releases

- Version 0.0.0.2 - Released 7/12/2011 - Lets you choose between asynchronous Jscript generation or synchronous

  • Version 0.0.0.1 - Released 04/2011 - First functional release

How do you use it.

It would be nice to just use fiddler for viewing traffic, but if you view the traffic on the new organization service endpoint  you can't see any of the traffic as it is encrypted. In the SDK, if you go to /SDK/helpercode/cs/client, there is a soaplogger solution included that allows you to capture decrypted SOAP envelope requests and responses that you send to and from the CRM 2011 SOAP Endpoint.

There is a tutorial on creating interactive web resource libraries from the soaplogger captured requests here:

http://msdn.microsoft.com/en-us/library/gg594434.aspx

When you finish with the soaplogger you get an output text that contains a request envelope in the "HTTP REQUEST" section of the document that might look like this:

<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">
      <request i:type="a:InsertOptionValueRequest" xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts">
        <a:Parameters xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
          <a:KeyValuePairOfstringanyType>
            <b:key>Label</b:key>
            <b:value i:type="a:Label">
              <a:LocalizedLabels>
                <a:LocalizedLabel>
                  <a:IsManaged i:nil="true" />
                  <a:Label>testoptionCode9</a:Label>
                  <a:LanguageCode>1033</a:LanguageCode>
                </a:LocalizedLabel>
              </a:LocalizedLabels>
              <a:UserLocalizedLabel i:nil="true" />
            </b:value>
          </a:KeyValuePairOfstringanyType>
          <a:KeyValuePairOfstringanyType>
            <b:key>AttributeLogicalName</b:key>
            <b:value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema%22%3Enew_testoptionset%3C/b:value>
          </a:KeyValuePairOfstringanyType>
          <a:KeyValuePairOfstringanyType>
            <b:key>EntityLogicalName</b:key>
            <b:value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema%22%3Eaccount%3C/b:value>
          </a:KeyValuePairOfstringanyType>
        </a:Parameters>
        <a:RequestId i:nil="true" />
        <a:RequestName>InsertOptionValue</a:RequestName>
      </request>
    </Execute>
  </s:Body>
</s:Envelope>

Ok, you can open the .exe file contained in the /bin/debug folder from the downloaded release now. Next, if you copy this envelope into the Soap Formatter UI (right now you have to do all copy and past and select-all operations using right click on the textbox in the Soap Formatter UI :( I am sure this will be fixed at some later point) and type in a namespace (we will use "EXAMPLE") and a function name (we will use "InsertOptionValue").  It should look like this:

https://lh6.googleusercontent.com/-dkoDcnWx8x4/TX-Jt0xTsSI/AAAAAAAAACA/QhjCtz3c9lc/s640/Example+SoapFormatter+1.png

Now if you hit generate it will transform the insert XML into the JScript call that is ready to be imported in CRM as a Web Resource and utilized on your forms. This is what it will look like using our example.

if (typeof (SDK) == "undefined")
   { SDK = { __namespace: true }; }
       //This will establish a more unique namespace for functions in this library. This will reduce the 
       // potential for functions to be overwritten due to a duplicate name when the library is loaded.
       SDK.EXAMPLE = {
           _getServerUrl: function () {
               ///<summary>
               /// Returns the URL for the SOAP endpoint using the context information available in the form
               /// or HTML Web resource.
               ///</summary>
               var OrgServicePath = "/XRMServices/2011/Organization.svc/web";
               var serverUrl = "";
               if (typeof GetGlobalContext == "function") {
                   var context = GetGlobalContext();
                   serverUrl = context.getServerUrl();
               }
               else {
                   if (typeof Xrm.Page.context == "object") {
                         serverUrl = Xrm.Page.context.getServerUrl();
                   }
                   else
                   { throw new Error("Unable to access the server URL"); }
                   }
                  if (serverUrl.match(/\/$/)) {
                       serverUrl = serverUrl.substring(0, serverUrl.length - 1);
                   } 
                   return serverUrl + OrgServicePath;
               }, 
           InsertOptionValueRequest: function () {
               var requestMain = ""
               requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
               requestMain += "  <s:Body>";
               requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
               requestMain += "      <request i:type=\"a:InsertOptionValueRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">";
               requestMain += "        <a:Parameters xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <b:key>Label</b:key>";
               requestMain += "            <b:value i:type=\"a:Label\">";
               requestMain += "              <a:LocalizedLabels>";
               requestMain += "                <a:LocalizedLabel>";
               requestMain += "                  <a:IsManaged i:nil=\"true\" />";
               requestMain += "                  <a:Label>testoptionCode9</a:Label>";
               requestMain += "                  <a:LanguageCode>1033</a:LanguageCode>";
               requestMain += "                </a:LocalizedLabel>";
               requestMain += "              </a:LocalizedLabels>";
               requestMain += "              <a:UserLocalizedLabel i:nil=\"true\" />";
               requestMain += "            </b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <b:key>AttributeLogicalName</b:key>";
               requestMain += "            <b:value i:type=\"c:string\" xmlns:c=\"http://www.w3.org/2001/XMLSchema\">new_testoptionset</b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <b:key>EntityLogicalName</b:key>";
               requestMain += "            <b:value i:type=\"c:string\" xmlns:c=\"http://www.w3.org/2001/XMLSchema\">account</b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "        </a:Parameters>";
               requestMain += "        <a:RequestId i:nil=\"true\" />";
               requestMain += "        <a:RequestName>InsertOptionValue</a:RequestName>";
               requestMain += "      </request>";
               requestMain += "    </Execute>";
               requestMain += "  </s:Body>";
               requestMain += "</s:Envelope>";
               var req = new XMLHttpRequest();
               req.open("POST", SDK.EXAMPLE._getServerUrl(), true)
               // 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");
               var successCallback = null;
               var errorCallback = null;
               req.onreadystatechange = function () { SDK.EXAMPLE.InsertOptionValueResponse(req, successCallback, errorCallback); };
               req.send(requestMain);
           },
       InsertOptionValueResponse: function (req, successCallback, errorCallback) {
               ///<summary>
               /// Receives the assign response
               ///</summary>
               ///<param name="req" Type="XMLHttpRequest">
               /// The XMLHttpRequest response
               ///</param>
               ///<param name="successCallback" Type="Function">
               /// The function to perform when an successful response is returned.
               /// For this message no data is returned so a success callback is not really necessary.
               ///</param>
               ///<param name="errorCallback" Type="Function">
               /// The function to perform when an error is returned.
               /// This function accepts a JScript error returned by the _getError function
               ///</param>
               if (req.readyState == 4) {
               if (req.status == 200) {
               if (successCallback != null)
               { successCallback(); }
               }
               else {
                   errorCallback(SDK.EXAMPLE._getError(req.responseXML));
               }
           }
       },
       _getError: function (faultXml) {
           ///<summary>
           /// Parses the WCF fault returned in the event of an error.
           ///</summary>
           ///<param name="faultXml" Type="XML">
           /// The responseXML property of the XMLHttpRequest response.
           ///</param>
           var errorMessage = "Unknown Error (Unable to parse the fault)";
           if (typeof faultXml == "object") {
               try {
                   var bodyNode = faultXml.firstChild.firstChild;
                   //Retrieve the fault node
                   for (var i = 0; i < bodyNode.childNodes.length; i++) {
                       var node = bodyNode.childNodes[i];
                       //NOTE: This comparison does not handle the case where the XML namespace changes
                       if ("s:Fault" == node.nodeName) {
                       for (var j = 0; j < node.childNodes.length; j++) {
                           var faultStringNode = node.childNodes[j];
                           if ("faultstring" == faultStringNode.nodeName) {
                               errorMessage = faultStringNode.text;
                               break;
                           }
                       }
                       break;
                   }
               }
           }
           catch (e) { };
        }
        return new Error(errorMessage);
     },
 __namespace: true
};

You can now parse in your form fields or values as needed and you can call the function in the eventhandler setup by using the syntax 

SDK.{namespace}.{function name}Request

or in our case:

SDK.EXAMPLE.InsertOptionValueRequest

I hope this helps! 

I also would love to have a few volunteers help me build the thing out further.

https://lh6.googleusercontent.com/-dkoDcnWx8x4/TX-Jt0xTsSI/AAAAAAAAACA/QhjCtz3c9lc/s320/Example+SoapFormatter+1.png