Thursday, 9 June 2011

CRM 2011 - Trigger Save On Silverlight Control

An issue that arises quite often when developing Silverlight controls for Microsoft Dynamics CRM 2011 is the ability to trigger a save on the Silverlight control when the form containing it is saved. The obvious method of doing this is to execute an exposed scriptable method on the silverlight controls itself to perform the update, and this is part way there.
Where an issue will arise is that supported method for saving records in silverlight is using either the SOAP or OData web services and the service model for silverlight is a asynchronous model.  This would mean the processes would appear as follows:
  1. Save button is clicked.
  2. Save is triggered on silverlight control
  3. Form save is under taken
  4. Form is refreshed.
This will cause an issue as the processing at step #3 will not wait until step #2 is completed.  This could cause the form to then refresh, which will end the web request and this may mean the silverlight save is cut off mid process. 
Because of this I have tried a number of options with no success, these include:
  1. Creating a javascript wait method to loop until the silverlight control is finish.  The issue with this method is that the javascript and silverlight control utilise the same thread and thus all you do is cause the browser to hang indefinitely.
  2. Putting a wait condition on the main thread within silverlight after the async process has started and to continue waiting until the process is complete.  The issue with this is the browsers main thread and the silverlight main thread are one and the same so there is nothing to tell the control that the time interval has elapse and to check again.  This will also cause the browser to hang.
  3. Attempt to wrap the whole process up to "fake" a synchronous process. This would cause the browser to appear to hang untill the process was complete.  This would partially work but not a friendly user experience.
With that said though with each failure you are one step closer to success, which happened to be the next option that was tried.... so here goes.


Process is as follows:
  1. User clicks save button.
  2. Javascript calls exposed method on silverlight control.
  3. Silverlight control checks if anything needs saving. If so...
    1. Provide feedback to user that control is saving.
    2. Cancel the save event on the form using the "preventDefault" method provided.
    3. Record the save method used to save using the "getSaveMethod" JScript method.
    4. Save the silverlight information.
    5. Set the silverlight control to "clean"
    6. Based on the save method call the JScript "save" method on the form.
What this will allow is the silverlight control to finish saving before the form saves, the "is dirty" function of the silverlight control will prevent the endless loop and finally there is no freezing of the users browser.


Example silverlight code:

        [ScriptableMember]
        public bool Save(object Context)
        {
            // only save a dirty control
            if (IsDirty && _AllowSave)
            {
                bsyIndicator.BusyContent = "Saving Changes...";
                bsyIndicator.IsBusy = true;
 
                ((dynamic)Context).preventDefault();
 
                _SaveMode = (Constants.SaveMode)int.Parse(((dynamic)Context).getSaveMode().ToString());
                
                // save the dirty control
                _recordHelper.Save_Complete += SaveComplete;
                _recordHelper.SaveRecord(record);
 
                return false;
            }
 
            IsDirty = true;
            return true;
        }


        private void SaveComplete(object sender, EventArgs e)
        {
            // do nothing on callback
            bsyIndicator.IsBusy = false;
 
            IsDirty = false;
 
            // call save again
            dynamic xrm = HtmlPage.Window.GetProperty("Xrm");
 
            switch(_SaveMode)
            {
                case Constants.SaveMode.Send:
                    xrm.Page.data.entity.save();
                    break;
                case Constants.SaveMode.SaveClose:
                    xrm.Page.data.entity.save("saveandclose");
                    break;
                case Constants.SaveMode.SaveNew:
                    xrm.Page.data.entity.save("saveandnew");
                    break;
                case Constants.SaveMode.SaveCompleted:
                case Constants.SaveMode.Deactivate:
                    // this is unsupported
                    HtmlPage.Window.Invoke("SaveAsCompleted");
                    break;
                default:
                    xrm.Page.data.entity.save();
                    break;
            }
 
            _RecordHelper.Save_Complete -= SaveComplete;
        }
 
Example JScript Code:

ExecuteSilverlightMethod = function (context, controlName, namespace, methodName) {
         var controlObj = Xrm.Page.ui.controls.get(controlName);
         silverLightControl = controlObj.getObject();
 
         eval("silverLightControl.Content." + namespace + "." + methodName + "(context.getEventArgs());");
     }

2 comments:

  1. Hi, your Post is very helpful.
    but there is a problem, Like you know when you Click on CRM save button it will automatically Create a Guid of the record, CreatedBy,CreatedOn, etc. but In the same time If I want to customize this record with the Name,Description,etc from silverlight It will also Create a new Guid , so we will have 2 separated record instead of One.

    ReplyDelete
    Replies
    1. Hi,
      The above code will allow you to "Do something" with Silverlight when the form is saved. From what I can assume in your comment what you want to do is save some values on the current record and not on a seperate record. If this is the case simply update the fields on the form (add them to the form and make them hidden).
      Use the following instead of saving a new record twice:
      Xrm.Page.data.entity.attributes.get("description").setValue(description);
      Xrm.Page.getAttribute("description").setSubmitMode("always");

      Delete