Tuesday, May 31, 2011

How to: Retrieve the Metadata for All Entities Using Jscript or .NET in Microsoft Dynamics CRM 2011

This illustration shows you how to retrieve metadata for all entities in an organization using SOAP (JScript) or C# (.NET) with the RetrieveAllEntities message against the CRM 2011 organization service.

IMPORTANT NOTE: This call didn't work very well for me as it returns A LOT of data and I couldn't even get it to work against CRM Online using EntityFilters.All.  I am guessing it would time out against on-premise also without some server side timeout tweaking.  Using just EntityFilters.Relationships the call took almost 90 seconds to complete and the resulting return message envelope was OVER 45,000 LINES LONG!
As a result I find that the normal RetrieveEntityRequest is a much more reasonable option and it performs fairly well, but it will only return one entity at a time.

Here is a link to where I cover the RetrieveEntityRequest that I do recommend using:
http://mileyja.blogspot.com/2011/05/how-to-retrieve-metadata-for-entity.html


Ok, anyways, lets look at the RetrieveAllEntitiesRequest Message.
First in C#:

RetrieveAllEntitiesRequest req = new RetrieveAllEntitiesRequest();
req.EntityFilters = EntityFilters.Privileges;
req.RetrieveAsIfPublished = true;
RetrieveAllEntitiesResponse resp = (RetrieveAllEntitiesResponse)service.Execute(req);


If you need help instantiating a service object in .NET check out this post:
http://mileyja.blogspot.com/2011/04/instantiating-service-object-within.html

It is important to note that you can filter on the type of entity metadata you want returned since the returned XML document will be very large if you choose EntityFilters.All as shown above.  Your choices are All, Attributes, Default, Entity, Privileges, and Relationships.

Now here is the Jscript nicely formatted by the CRM 2011 SOAP formatter. Available at: http://crm2011soap.codeplex.com/

Now in Jscript:

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;
               }, 
           RetrieveAllEntitiesRequest: 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:RetrieveAllEntitiesRequest\" 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>EntityFilters</b:key>";
               requestMain += "            <b:value i:type=\"c:EntityFilters\" xmlns:c=\"http://schemas.microsoft.com/xrm/2011/Metadata\">Privileges</b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <b:key>RetrieveAsIfPublished</b:key>";
               requestMain += "            <b:value i:type=\"c:boolean\" xmlns:c=\"http://www.w3.org/2001/XMLSchema\">true</b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "        </a:Parameters>";
               requestMain += "        <a:RequestId i:nil=\"true\" />";
               requestMain += "        <a:RequestName>RetrieveAllEntities</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.RetrieveAllEntitiesResponse(req, successCallback, errorCallback); };
               req.send(requestMain);
           },
       RetrieveAllEntitiesResponse: 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) {
               if (successCallback != null)
               { successCallback(); }
               }
               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
};




To understand how to parse the response please review my post on using the DOM parser.
Now you can call the SDK.SAMPLES.RetrieveAllEntitiesRequest function from your form jscript handler.
Thats all there is to it!

I hope this helps!

4 comments:

  1. Hi, I found this code very useful but I need to retrieve only the entities display name through Entity Filters but couldn't manage to do it. Maybe there's an easier way? I have no knowledge of SOAP request. Could you please tell how can this be achieved?

    ReplyDelete
  2. Entityfilters.Entity should return the entity name, display name, etc... and the details you need. I still recommend using RetrieveEntityRequest (linked above) instead of this request because it's very slow and bloated to pull all the entities at once. There isn't a granular way that I am aware of to only pull the display name. Entityfilters.Entity though won't pull a lot of the other junk like attributes and relationships you don't need.

    ReplyDelete
    Replies
    1. Yes, I've been trying with EntityFilters.Entity but the thing is I need to fill an Option Set with the display name for whatever entities are currently published. That's way RetrieveEntityRequest won't work for me. I just thought I could somehow dispose of the entity name and fill the Option Set with the display name only. Thanks anyway.

      Delete