Wednesday, March 30, 2011

Retrieve Web Resource Contents in Microsoft CRM 2011 in Jscript or .NET

I already showed everyone how to update web resource contents dynamically in Jscript and in C#, now how about retrieving them.  I saw this question come across the forum and figured I would address it more directly.

Here is what it involves:

1. perform a RetrieveMultiple call to pull back your web resource entity.
2. Decode the "content" property from base-64 to a string.

First in C#:  I am using the SoapLogger solution to do this because it makes figuring out the jscript for this (which I will show you in a minute) much easier.

 SoapLoggerOrganizationService slos = new SoapLoggerOrganizationService(serverConfig.OrganizationUri, service, output);

      //Add the code you want to test here:
      // You must use the SoapLoggerOrganizationService 'slos' proxy rather than the IOrganizationService proxy you would normally use.


      RetrieveMultipleRequest rmr = new RetrieveMultipleRequest();
      RetrieveMultipleResponse resp = new RetrieveMultipleResponse();
         WebResource wb = new WebResource();
         
      QueryExpression query = new QueryExpression()
      {
          EntityName = "webresource",
          ColumnSet = new ColumnSet("content"),
          Criteria = new FilterExpression
          {
              FilterOperator = LogicalOperator.And,
              Conditions = 
                {
                    new ConditionExpression
                    {
                        AttributeName = "webresourceid",
                        Operator = ConditionOperator.Equal,
                        Values = { "ADFA33CB-7050-E011-9CF8-080027AD5B6E" }
                    }
                }
          }
      };

        rmr.Query = query;
        resp  = (RetrieveMultipleResponse)slos.Execute(rmr);
        wb =  (WebResource)resp.EntityCollection.Entities[0];

        byte[] b = Convert.FromBase64String(wb.Content);

        //**************************************
        //The string below will contain your HTML
        //**************************************
        string strHTML = System.Text.Encoding.UTF8.GetString(b);


Now, let's see it in Jscript. I formatted up the jscript for the retrievemultiple call using the CRM 2011 Jscript Soap Formatter Tool. It basically pulls back the response from the soap call and parses out the element we are looking for and alerts it to the screen.

function runme() {
    var guidWebResource = "ADFA33CB-7050-E011-9CF8-080027AD5B6E";
    SDK.SAMPLES.RetrieveWebResourceRequest(guidWebResource);
}

//is called by the soap response and parses the response, finds our "content" property and then alerts our content value
function parseResponse(responseXML) {

    
    xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
    xmlDoc.async="false";
    xmlDoc.loadXML(responseXML);
    x=xmlDoc.getElementsByTagName("a:KeyValuePairOfstringanyType");
    for (i=0;i<x.length;i++)
   {
      if (x[i].childNodes[0].text =="content")
      {
         //we decode the base 64 contents and alert the HTML of the Iframe
          alert(decode64(x[i].childNodes[1].text));
      }
      
   } 
}
 
 function decode64(input) {
    var keyStr = "ABCDEFGHIJKLMNOP" +
               "QRSTUVWXYZabcdef" +
               "ghijklmnopqrstuv" +
               "wxyz0123456789+/" +
               "=";

    var output = "";
    var chr1, chr2, chr3 = "";
    var enc1, enc2, enc3, enc4 = "";
    var i = 0;

    // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
    var base64test = /[^A-Za-z0-9\+\/\=]/g;
    if (base64test.exec(input)) {
        alert("There were invalid base64 characters in the input text.\n" +
              "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" +
              "Expect errors in decoding.");
    }
    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

    do {
        enc1 = keyStr.indexOf(input.charAt(i++));
        enc2 = keyStr.indexOf(input.charAt(i++));
        enc3 = keyStr.indexOf(input.charAt(i++));
        enc4 = keyStr.indexOf(input.charAt(i++));

        chr1 = (enc1 << 2) | (enc2 >> 4);
        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
        chr3 = ((enc3 & 3) << 6) | enc4;

        output = output + String.fromCharCode(chr1);

        if (enc3 != 64) {
            output = output + String.fromCharCode(chr2);
        }
        if (enc4 != 64) {
            output = output + String.fromCharCode(chr3);
        }

        chr1 = chr2 = chr3 = "";
        enc1 = enc2 = enc3 = enc4 = "";

    } while (i < input.length);

    return unescape(output);
}


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.SAMPLES = {
           _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;
               }, 
           RetrieveWebResourceRequest: function (webResourceGuid) {
               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:RetrieveMultipleRequest\" 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>Query</b:key>";
               requestMain += "            <b:value i:type=\"a:QueryExpression\">";
               requestMain += "              <a:ColumnSet>";
               requestMain += "                <a:AllColumns>false</a:AllColumns>";
               requestMain += "                <a:Columns xmlns:c=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">";
               requestMain += "                  <c:string>content</c:string>";
               requestMain += "                </a:Columns>";
               requestMain += "              </a:ColumnSet>";
               requestMain += "              <a:Criteria>";
               requestMain += "                <a:Conditions>";
               requestMain += "                  <a:ConditionExpression>";
               requestMain += "                    <a:AttributeName>webresourceid</a:AttributeName>";
               requestMain += "                    <a:Operator>Equal</a:Operator>";
               requestMain += "                    <a:Values xmlns:c=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">";
               requestMain += "                      <c:anyType i:type=\"d:string\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">" + webResourceGuid + "</c:anyType>";
               requestMain += "                    </a:Values>";
               requestMain += "                  </a:ConditionExpression>";
               requestMain += "                </a:Conditions>";
               requestMain += "                <a:FilterOperator>And</a:FilterOperator>";
               requestMain += "                <a:Filters />";
               requestMain += "              </a:Criteria>";
               requestMain += "              <a:Distinct>false</a:Distinct>";
               requestMain += "              <a:EntityName>webresource</a:EntityName>";
               requestMain += "              <a:LinkEntities />";
               requestMain += "              <a:Orders />";
               requestMain += "              <a:PageInfo>";
               requestMain += "                <a:Count>0</a:Count>";
               requestMain += "                <a:PageNumber>0</a:PageNumber>";
               requestMain += "                <a:PagingCookie i:nil=\"true\" />";
               requestMain += "                <a:ReturnTotalRecordCount>false</a:ReturnTotalRecordCount>";
               requestMain += "              </a:PageInfo>";
               requestMain += "              <a:NoLock>false</a:NoLock>";
               requestMain += "            </b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "        </a:Parameters>";
               requestMain += "        <a:RequestId i:nil=\"true\" />";
               requestMain += "        <a:RequestName>RetrieveMultiple</a:RequestName>";
               requestMain += "      </request>";
               requestMain += "    </Execute>";
               requestMain += "  </s:Body>";
               requestMain += "</s:Envelope>";
               var req = new XMLHttpRequest();
               req.open("POST", SDK.SAMPLES._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.SAMPLES.RetrieveWebResourceResponse(req, successCallback, errorCallback); };
               req.send(requestMain);
           },
       RetrieveWebResourceResponse: function (req, successCallback, errorCallback) {
               ///<summary>
               /// Recieves the assign response
               ///</summary>
               ///<param name="req" Type="XMLHttpRequest">
               /// The XMLHttpRequest response
               ///</param>
               ///<param name="successCallback" Type="Function">
               /// The function to perform when an successfult 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) {
               
                  parseResponse(req.responseXML.xml);
               
               
               }
               else {
                   errorCallback(SDK.SAMPLES._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
};


I hope this helps!

6 comments:

  1. Hi ! Iwas trying to get help from your code to use Webresouces as you do , but when i try to do this:

    "wb = (WebResource)resp.EntityCollection.Entities[0];"

    i dont have the assembly for it , already tryed every thing but , was not able to find it ...

    can you tell me what is the assembly that I need ?
    thank you in advance,
    Regards,
    Ricardo Serra

    ReplyDelete
    Replies
    1. If you look at examples in the sdk you will find a types file in many of the files. This usually contains the code for the out of box entites. You can use CrmSvcUtil.exe included with the SDK to generate your own strongly typed assemblies for your entities.

      The other options is to just use entity and then call the attributes by name as strings.

      Example:

      Entity wb = resp.EntityCollection.Entities[0];
      call content attribute as string by using wb["content"]

      Delete
  2. Thanks for such a helpful post.

    ReplyDelete
  3. How can we get the webresourceid value as mentioned in the post, Values = { "ADFA33CB-7050-E011-9CF8-080027AD5B6E" } ?

    ReplyDelete
  4. Tried the same, unable to retrieve complete XML file. It is getting truncated. Please help

    ReplyDelete