Making Up - Getting your Dynamics CRM Forms and Silverlight Web Resources talking to each other

I’ve been working on a number of projects recently where I’ve needed to use Silverlight web resources embedded within Dynamics CRM entity forms to extend the user interface. Sometimes you can get away with passive integration – a standalone piece of functionality delivered by a Silverlight web resource but in my scenarios I’ve needed interaction between the form and the Silverlight web resource.

The information and code examples you need to achieve this are all available but they are dispersed across the CRM SDK documentation and Silverlight/HTML MSDN documentation. This article illustrates how to achieve bi-directional interaction between your CRM Form and embedded Silverlight Web resource.

When embedding Silverlight web resources within CRM Forms, it is common to want to do the following:-

  • Make requests from inside the Silverlight control to the containing form
  • Enable the form JScript to trigger events on the Silverlight control
  • Dynamically register linkage between the Silverlight control and containing CRM Form for reusable controls

Rather than providing an end to end scenario, I intend this article to demonstrate the key building blocks that you can use and where they are appropriate. This should mean we stay focussed on the content rather than becoming blinkered by a particular scenario. Throughout the following examples, assume that you have an entity form with a Silverlight web resource embedded directly on the form.

Details of the HTML Bridge that enable the interaction between HTML and Managed code can be found at https://msdn.microsoft.com/en-us/library/cc645076(VS.95).aspx

Invoking JScript functions from Silverlight

Before we move on with the sample, it is worth noting that it is recommended that you provide a namespace for your functions in order to ensure that they are not overwritten at load time by a similarly named function within another library. Full details can be found at https://msdn.microsoft.com/en-us/library/gg309562.aspx#Y1068

Consider the following JScript library:

 if (typeof (MCS) == "undefined"){ MCS = {}; }  
// Create Namespace container for functions in this library;  
// 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. 
MCS.InteractionFunctions = {      
    performAction: function (aParameter) {          
        // Code to perform your action.          
        alert(aParameter);      
    }
};

To invoke the performAction function from Silverlight we need to retrieve the object representing the namespace containing the function we wish to invoke. Using the HTML Bridge we can do the following:

 using System.Windows.Browser;
// Code omitted for brevity
ScriptObject interactionFunctions = HtmlPage.Window.Eval("MCS.InteractionFunctions") as ScriptObject;
if (interactionFunctions != null)
{    
    interactionFunctions.Invoke("performAction", "Hello World!");
}

Since the Invoke Method returns an object you can also return data from the JScript function (i.e. an attribute value).

Note: Had this been a global function (not in a namespace and not recommended) we could invoke this method directly as follows:

 HtmlPage.Window.Invoke("performAction", "Hello World!");

Uses

There are many uses for invoking JScript functions from Silverlight and I imagine that everyone will think of many more than I can. A couple of obvious scenarios are to initiate a Save of the form or to update a field on the form directly from your embedded Silverlight web resource. One use that we will see in more detail later is the ability to dynamically attach an event handler to the onChange event of a field on the form from Silverlight.

Invoking Silverlight methods from JScript

Your Silverlight application must expose the type including the properties, event handlers and methods you require to be available to your JScript library. In order to achieve this you must mark them as scriptable.

Within your Silverlight application, open the class file for the type you wish to expose (in my example I use the MainPage class) and add the relevant using statement:

 using System.Windows.Browser;

Mark the type as scriptable as follows:

 [ScriptableType()]
public partial class MainPage : UserControl
{
    // Code omitted for brevity
}

Register the object to expose as a Scriptable Object:

 private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    HtmlPage.RegisterScriptableObject("interactionObject", this);
}

In the example, the scriptable object is registered when the MainPage has loaded. Since the ScriptableType has been defined as the MainPage class, it is used directly when registering by specifying “this” in the code above. The first parameter “interactionObject” represents the identifier that will be used when calling the scriptable object from JScript.

To expose a public method to JScript, mark the method as scriptable as follows:

 [ScriptableMember()]
public void FormAttributeChanged(string field, string value)
{
    // Take some action based on the attribute that has changed and it's new value.
    MessageBox.Show("Field: " + field + "\nhas changed value to: " + value);
}

Finally, to invoke the scriptable Silverlight method from JScript…

Retrieve the form element representing the embedded Silverlight web resource from the form:

 var control = Xrm.Page.getControl('WebResource_SilverlightClient');

Retrieve the Silverlight object contained within form element:

 var silverlightPlugin = control.getObject();

Invoke the scriptable method on the Silverlight component through the scriptable object (in this case “interactionObject”) that you have registered:

 // Code for attName and attValue omitted for brevity
silverlightPlugin.Content.interactionObject.FormAttributeChanged(attName, attValue);

The example above can be further extended for the case of the form save event. Consider the scenario where you need to validate content maintained within the Silverlight web resource prior to allowing the user to save the update or creation of the entity instance. For example, imagine you have an entity representing a mortgage application and the embedded Silverlight control calculates a credit risk. You need to ensure that the calculated risk is within an acceptable range prior to creating the mortgage application record.

Let’s create the stub of a JScript function for this purpose:

 canSave: function (executionContext) {
}

Attach the function to the onSave event of the form, ensuring that you tick the option to “Pass execution context as first parameter”. The purpose of passing the execution context is to enable us to have access to the methods that can be used to manage the save event – in this case, preventing it. Full details can be found at https://msdn.microsoft.com/en-us/library/gg509060.aspx

Within our JScript function we repeat the process we used above to invoke a validation method on the Silverlight component as follows:

 var control = Xrm.Page.getControl('WebResource_SilverlightClient');
var silverlightPlugin = control.getObject();
silverlightPlugin.Content.interactionObject.CheckCreditRisk();

You can imagine that the Silverlight method CheckCreditRisk() (marked as a ScriptableMember) performs some calculation and then returns true if acceptable and false otherwise. The complete JScript function to prevent the save action completing if the Credit risk is too high is shown below:

 canSave: function (executionContext) {
    var control = Xrm.Page.getControl('WebResource_SilverlightClient');
    var silverlightPlugin = control.getObject();
    if (!silverlightPlugin.Content.interactionObject.CheckCreditRisk()) {
        alert('Credit Risk is too high');
        executionContext.getEventArgs().preventDefault();
    }
}

Dynamically registering and handling field onChange events

Attaching a JScript event handler for a field onChange event dynamically and subsequently handling the event within the Silverlight component requires a combination of the approaches detailed above:

Define a method on the Silverlight component to handle the event:

 [ScriptableMember()]
public void TextAttributeChanged(string field, string value) 
{

}

Follow the steps detailed in “Invoking Silverlight methods from JScript” above to expose the scriptable object and method to JScript.

In the loaded event for the page within the Silverlight component, invoke a JScript method to attach an event handler to the onChange event of the relevant CRM Field(s):

 void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    // Get the namespace of the function to invoke:
    ScriptObject interactionFunctions =  
        HtmlPage.Window.Eval("MCS.InteractionFunctions") as ScriptObject;

    if (interactionFunctions != null)
    {
        interactionFunctions.Invoke("addMessageToOnChange", "mcs_name");
        interactionFunctions.Invoke("addMessageToOnChange", "mcs_description");
    }
}

Note: You can imagine that for a flexible Silverlight component (used by multiple entities) you could determine which fields to dynamically add onChange event handlers to from an XML Web Resource which could be entity specific.

In the JScript library, implement the corresponding function to attach a dynamic function to the onChange event of the relevant CRM Field:

 addMessageToOnChange: function (field) 
{
    if (field == null || field == "" || field == "undefined") return;

    var attribute = Xrm.Page.data.entity.attributes.get(field);
    var tempFunc = function () {
    MCS.InteractionFunctions.textAttributeChanged(attribute);
    };
    
    attribute.addOnChange(tempFunc);
};

In the JScript library, implement the dynamically attached event handler function (in this example “textAttributeChanged”) which invokes a method on the Silverlight component:

 textAttributeChanged: function (attribute) 
{
    if (attribute == null || attribute.getAttributeType() != "string") 
    {
        return;
    }

    var control = Xrm.Page.getControl('WebResource_SilverlightClient');

    if (control == null) 
    {
        return;
    }

    var silverlightPlugin = control.getObject();

    if (silverlightPlugin != null) 
    {
        silverlightPlugin.Content.interactionObject.AttributeChanged(
            attribute.getName(),
            attribute.getValue());
    }
};

Uses

A couple of obvious uses for attaching onChange event handlers dynamically are when you are re-using the Silverlight component across multiple entities. Perhaps you need to be sensitive to a particular selection that a user has made and present different information within the Silverlight control, or perhaps you have implemented a custom form of duplicate detection.

Summary

We have seen in this article that it is relatively simple and straight forward for the Entity form and an embedded Silverlight web resource to interact with each other without the need for round trips to the server. Using both technologies in unison provides a compelling user experience in a supported manner.

Phil

Phil Hand Principal Consultant Microsoft Consulting Services UKView my bio