Thursday, May 26, 2011

Clone or Copy an Entity Form Using .NET or Jscript in Microsoft Dynamics CRM 2011

This illustration shows you how to copy or clone an entity form in Microsoft Dynamics CRM 2011 using the CopySystemFormRequest.   This example will be given in SOAP (JScript) and in C# (.NET).

This will allow you to create a clone of a form so that you can modify a copy instead of the original and then you can make it default or whatever you want.  Unfortunately because all of the attributes need to line up this only really works for copying the form to the same entity.

Ok, here is what the code look like!
First in C#:

CopySystemFormRequest req = new CopySystemFormRequest();

//what entity form do we want to copy
req.SourceId = new Guid("894CC46A-B0CB-4AB0-8BF6-200544E46A2D");

CopySystemFormResponse resp =  (CopySystemFormResponse)service.Execute(req);

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

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;
               }, 
           CopySystemFormRequest: 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=\"b:CopySystemFormRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
               requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <c:key>SourceId</c:key>";
               requestMain += "            <c:value i:type=\"d:guid\" xmlns:d=\"http://schemas.microsoft.com/2003/10/Serialization/\">894cc46a-b0cb-4ab0-8bf6-200544e46a2d</c:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "        </a:Parameters>";
               requestMain += "        <a:RequestId i:nil=\"true\" />";
               requestMain += "        <a:RequestName>CopySystemForm</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.CopySystemFormResponse(req, successCallback, errorCallback); };
               req.send(requestMain);
           },
       CopySystemFormResponse: 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.CopySystemFormRequest function from your form jscript handler.
Thats all there is to it!

I hope this helps!

6 comments:

  1. an interesting blog post. The one danger I have noticed when I have tried to clone entities is the other linked entities which you cannot copy as easily, especially if there is more than one of them.

    ReplyDelete
  2. Yes, this isn't truly an entity clone though either, it's just an entity form clone. Basically it allows you to make a copy of your form on the same entity to be able to extend without messing up the original. It could be used for mock-ups or for development of a form that you want to use as the main form later.

    The subject of actual entity cloning would be very useful but one that is a lot more difficult and extensive than the scope of this post.

    ReplyDelete
  3. I understand that this approach only clones the entity form. How about the related records and activities associated in the source entity ?? We can't do that rite. ?. Hence, this is not considered as a complete clone of an entity...

    ReplyDelete
  4. There is another very most eaier way to do this clone. I had done this in my application.

    1) You could just make a referential relationship to the same entity, and do the attribute mapping in the relationship and publish.

    2) After the above, you will see the same entity link at the left nevigation pane,go there and click on new button and you will see that new windown opens with the mapped attributed auto populated.

    3) What you can do is, copy the URL of the window and have a button in the main entity form and call this copied URL and pass the Guid into it and it will open the new entity record with the mapped fields populated in it.

    4) Hence, the clone form functionality is achieved.

    ReplyDelete
  5. Yes, this is supposed to be a helper in how to achieve this programmatically. You are also correct in saying this is not a complete entity clone, if you wanted to completely clone an entity you would need to develop a program or tool to do it based on the SDK.

    ReplyDelete
  6. Hi Jamie
    We still run CRM 2011 RU 10. Our project FORM has grown in the last years. Its actually pretty confusing...now I wanna try our Java-Goodie. I don't have any deeper IT-background. But I try to learn more about CRM. Can you explain me how to set up your JAVA-Tool? Do I have to import it as a solution? thanks for help.

    ReplyDelete