Thursday, July 14, 2011

Set Status or State of a Record Using Jscript or .NET in Microsoft Dynamics CRM 2011 With SetStateRequest

This illustration shows how to set the state or status and also the status reason of an entity record in Microsoft Dynamics CRM 2011 in code in jscript and also C#  using the SetStateRequest.   This example will be given in SOAP (JScript) and in C# (.NET).  The example given will be that of deactivating an account record.

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

SetStateRequest req = new SetStateRequest();

//the entity you want to change the state of
req.EntityMoniker = new EntityReference("account", new Guid("DCB1D9CD-1DAE-E011-8CBC-1CC1DE7955DB"));

//what should the new state be
req.State = new OptionSetValue((int)AccountState.Inactive);

//Pick an option from the status reason picklist to specify reason for state change
req.Status = new OptionSetValue(2);

SetStateResponse resp = (SetStateResponse)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:


This is example is asynchronous, if you want to learn how to make JScript SOAP calls synchronously please visit this posthttp://mileyja.blogspot.com/2011/07/using-jscript-to-access-soap-web.html

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;
               }, 
           SetStateRequest: 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:SetStateRequest\" 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>EntityMoniker</c:key>";
               requestMain += "            <c:value i:type=\"a:EntityReference\">";
               requestMain += "              <a:Id>dcb1d9cd-1dae-e011-8cbc-1cc1de7955db</a:Id>";
               requestMain += "              <a:LogicalName>account</a:LogicalName>";
               requestMain += "              <a:Name i:nil=\"true\" />";
               requestMain += "            </c:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <c:key>State</c:key>";
               requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
               requestMain += "              <a:Value>1</a:Value>";
               requestMain += "            </c:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <c:key>Status</c:key>";
               requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
               requestMain += "              <a:Value>2</a:Value>";
               requestMain += "            </c:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "        </a:Parameters>";
               requestMain += "        <a:RequestId i:nil=\"true\" />";
               requestMain += "        <a:RequestName>SetState</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.SetStateResponse(req, successCallback, errorCallback); };
               req.send(requestMain);
           },
       SetStateResponse: 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.SetStateRequest function from your form jscript handler.
Thats all there is to it!

I hope this helps!

19 comments:

  1. I spent hours trying to use SOAP to deactivate an account and this was the solution that worked! Thanks a lot.

    ReplyDelete
  2. I am glad to hear this worked well for you!

    ReplyDelete
  3. This is great. How might I customize this to deactivate all records instead of just 1? I tried removing the hard-coded id line in the soap call and that didn't seem to work. Thanks!

    ReplyDelete
  4. You need to do a retrievemultiple call and then loop through the results and make a similiar SetStateRequest for each result.

    ReplyDelete
  5. OK so far I was able to use the soaplogger in the SDK to run my c# routine to deactivate records and get the raw soap requests. Problem here is that now I have 5 soap requests (1 for each record). Where/how do I retrievemultiple in the javascript?

    ReplyDelete
  6. You need to do the retrievemultiple request first in the soap logger and generate a synchronous jscript request in the soap formatter tool I created for the jscript retrievemultiple calls. That will return a list of entities. Then you need to parse and iterate the list and make a set state request for eachone.

    So It's kind of like:
    so the rough logic looks like this:
    1. execute retrievemultiple synchronously

    2. parse and iterate response xml and call setstaterequests
    basic logic:
    foreach(entity e in retrievemultiple response)
    {
    execute setstate request for e.id
    }

    Does this makes sense or am I completely missing the issue you are having.

    ReplyDelete
  7. I cannot get this code to work for the Lead entity. Do you perhaps know if it does not work on the Lead? Any help will be much appreciated.

    ReplyDelete
    Replies
    1. According to the SDK documentation it should work on Lead just fine. http://msdn.microsoft.com/en-us/library/microsoft.crm.sdk.messages.setstaterequest.aspx
      You should open a question at http://social.microsoft.com/Forums/en-US/crmdevelopment/threads and post your code and shoot me a line with the link to the question. It's probably just a syntax error or else your lead is already in a state that doesn't allow your transition. Make sure you make note of any error you are receiving also when you ask the question.

      Delete
  8. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. Thanks Jamie. I can get the code to work on accout, it actually goes into the successCallback but the state of the lead always open, does not change. I will log and forward you the link. Just seems way too much code to do something so simple. I prefer javascript to c# and plugin code which would be easy, anyway, i will keep you posted.

      Delete
  9. I'm using the same SOAP javascript code to mark complete the record and one plug-in is triggering on post mark complete(SetStateDynamicEntity). This plug-in giving error with this SOAP code. But its working fine when that plug-in has disabled, and plug-in doesn't give any error with SaveAsComplete() javascript code to mark complete the record. Can u tell me whats wrong between this SOAP code and plug-in..?

    ReplyDelete
    Replies
    1. If you are using SetStateDynamicEntity then you are using CRM 4.0 I am guessing. The code above is only applicable to the 2011 Organization service endpoint for CRM 2011. It will not work with 4.0 and it won't work in 2011 against the old 4.0 endpoint.

      Delete
    2. This comment has been removed by the author.

      Delete
    3. And one more thing is, I need to save the form before mark it completed. Then, any idea about how can we achieve this functionality in CRM 2011?

      Delete
    4. In your onload for jscript or in your post update for a plugin you could test for certain conditions and then perform the setstaterequest

      Delete
  10. Dear Jamie

    I am trying to update the status of phone call activity using setstaterequest in javascript the code is working fine.. its not giving any error but the status is not getting updated .. it still remains the same as open only do u have any idea on this ..

    ReplyDelete
    Replies
    1. If you are on that entities form you might have to reload it before it displays with the change. You can do the reload from jscript easily also.

      Delete
  11. Hi, Quick question don't know what I am missing but I get an error saying SDK is not defined. Any pointers?

    ReplyDelete
    Replies
    1. Posted a thread for this question for your follow up as well.
      http://social.microsoft.com/Forums/en-US/crmdevelopment/thread/e1d695e0-7fc2-4180-b483-222243887f7b

      Thanks!

      Delete