Browser Interoperability with Silverlight (cont'd)

XAML, Managed Code, and JavaScript

JavaScript functions can gain access to the content of the Silverlight application and perform read and write operations. The content of the Silverlight application is the tree of XAML elements.

Anything in the XAML document that is characterized by a unique name—the x:Name attribute—can be accessed and scripted in JavaScript. The first step entails getting a DOM reference to the Silverlight plug-in. In an ASP.NET AJAX page, you would use the following code:

var plugin = $get("SilverlightControl1");

SilverlightControl1 is the ID of the Silverlight control or the ID you used for the <object> tag that points to the downloadable content. Next, you point to the actual XAML content using the content property. To locate specific XAML elements, you use the findName method:

// Retrieve the XAML element tagged with the name of TextBlock1
var xamlTree = $get("SilverlightControl1").content;
var textBlock1 = xamlTree.findName("TextBlock1");
// Modify the current content of the text block element
textBlock1.Text = "...";

If you use JavaScript code to drive the content of the XAML document, then it is recommended that you cache in your page any reference to XAML elements that you encounter along the way. This would save you from repeatedly traversing the XAML tree to find the same element over and over again.

You should notice that using JavaScript to access the content of the XAML document is a bit outdated for Silverlight 2. It remains a good option if you are targeting Silverlight 1.0 or, at least, if you are serving up Silverlight applications consisting of only XAML and script code. If you can use managed code to decide about the content to display, then it is hard to imagine any changes that you might want to make to the user interface using JavaScript instead of managed code.

In Silverlight 2, you can use JavaScript code to invoke managed code. This option addresses a scenario where you have an HTML-based page powered by some managed code that performs critical operations.

Your JavaScript code can script any managed object hosted in Silverlight 2 that has been previously registered as scriptable:

guidHelper = new GuidHelper();
HtmlPage.RegisterScriptableObject("GuidTools", guidHelper); 

The method RegisterScriptableObject takes the informal name of the object being registered and a variable instance. The string being the first argument is the name that the caller JavaScript code will use to refer to the registered object. With respect to the preceding code, the following shows what you do from JavaScript in order to invoke a method on the sample class GuidHelper:

// Invoke a method on a scriptable managed object
var guid = $get("Silverlight1").content.GuidTools.Generate();
// Update the user interface with the results
$get("Random1").innerHTML = guid; 

The public name of the scriptable object—in this case, GuidTools—is used as a member to access the underlying object from JavaScript. The pseudo property, GuidTools, is exposed by the content property of the Silverlight plug-in. Let's have a look at the GuidHelper class:

public class GuidHelper
{
[ScriptableMember]
public string Generate()
{
Guid g = Guid.NewGuid();
return g.ToString();
}
}

The ScriptableMember attribute on public methods of a scriptable class indicates that the method can be invoked from JavaScript. If all public methods of a class are scriptable, you can use the Scriptable¬Type attribute on the class to automatically extend the scriptability attribute to all public methods.

An object registered as scriptable is instantiated through managed code and an existing instance is passed down to JavaScript. Not all managed types can be directly instantiated in JavaScript.

Before you can create an instance of a managed class from JavaScript, you first need to register the type you want as available for creation from JavaScript code:

HtmlPage.RegisterCreateableType("StockPicker", typeof(Samples.Order)); 

The first argument indicates the alias for the type to be used from within JavaScript. Here's how you create an instance of the Samples.StockPicker class:

var plugin = $get("Silverlight1");
var type = "Samples.StockPicker";
var inst = plugin.content.services.createObject(type); 

You retrieve the Silverlight plug-in, access the content.services property and then invoke the method createObject. The argument to createObject is the string with the name of the managed type to instantiate.

A scriptable type that has complex and custom types in the signatures of its methods also works as a factory for its types. For example, imagine a managed type Customer with the following method:

void Add(Order order); 

What about the Order type? Should it be declared createable? Not necessarily. If you omit the call to RegisterCreateableType, then you can only use as the factory a scriptable type that has a scriptable member that requires that type. In other words, if Customer is registered as scriptable and method Add is scriptable, then you can instantiate Order from JavaScript without declaring the type creatable. The following code would work:

var customer = $get("Silverlight1").content.Customer;
var order = customer.createManagedObject("Order");
order.ID = 1;
o.OrderDate = new Date();
customer.Add(order); 

Managed types are marshalled down to JavaScript wrapped as browser objects. The wrapper browser object contains a table of valid methods to call from JavaScript. The list includes all methods declared as scriptable, plus createManagedObject. Invocations on methods are then resolved by invoking the corresponding method in the managed code.

Cross-Domain Access and Silverlight Plug-Ins

A Silverlight application that exposes a public managed API is subject to cross-domain access. If cross-domain access represents a security hazard, or just an unwanted feature, you should take your countermeasures.

When a page with a Silverlight plug-in is hosted in a frame, it is possible that the host page lives in a different domain than the Silverlight application. This means that the JavaScript code in the host page (cross-domain) is able to gain access through the frame to the Silverlight plug-in and script any public-managed object.

Cross-domain access to the Silverlight content may be disabled, fully enabled, or limited to scripting. It is disabled by default. You can control cross-domain access, as in Figure 6, by using the External¬CallersFromCrossDomain attribute in the Deployment node of the Silverlight application manifest file. The manifest file is the file that is generated by Visual Studio 2008 when you compile a Silverlight project.

Figure 6 A Sample Manifest File for a Silverlight 2 App

<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
EntryPointAssembly="BrowserFun"
EntryPointType="BrowserFun.App"
RuntimeVersion="2.0.30523.6"
ExternalCallersFromCrossDomain="FullAccess">
<Deployment.Parts>
<AssemblyPart x:Name="BrowserFun"
Source="BrowserFun.dll" />
<AssemblyPart x:Name="System.Windows.Controls.Extended"
Source="System.Windows.Controls.Extended.dll" />
</Deployment.Parts>
</Deployment>

When you load a page in the browser that contains one or more Silverlight plug-ins, you still get one instance of the CLR per browser process. However, each running instance of the Silverlight plug-in gets its own AppDomain. Nothing is being shared between plug-ins and communication is possible, but only through code—and with the help of a little trick.

The trick consists essentially of using the browser interoperability layer as an intermediary. One plug-in exposes a managed interface and the other connects to it and invokes methods. So one plug-in defines a few scriptable members, like so:

public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
HtmlPage.RegisterScriptableObject(
"Action", new ActionPageCommand());
}
}

The ActionPageCommand class contains all methods that the plug-in exposes for external callers. Here's an example:

 [ScriptableType]
public class ActionPageCommand
{
public int GetRandomNumber()
{
Random rnd = new Random();
return rnd.Next(0, 100);
}
}

Scriptable members on a plug-in are directly visible to JavaScript code in the page. In Figure 7, you see a JavaScript wrapper for the publicly available interface of the Silverlight plug-in. Any plug-in interested in the interface of the other plug-in can create an instance of the JavaScript wrapper in the figure:00

ScriptObject so = HtmlPage.Window.CreateInstance("ActionBar");
object result = so.Invoke("invokeGetRandomNumber");

Figure 7 Exposure to Another Silverlight Plug-In

<script type="text/javascript">
var ActionBar = function()
{}
function invokeGetRandomNumber$Impl()
{
var plugin2Services = $get("Silverlight2").content;
var results = plugin2Services.Action.GetRandomNumber();
return results;
}
ActionBar.prototype =
{
invokeGetRandomNumber: invokeGetRandomNumber$Impl
}
</script>

Is there a more direct way to invoke a Silverlight plug-in from another one? Yes, you can opt for the following:

HtmlElement plugin = HtmlPage.Document.GetElementById("Silverlight2");
var content = (ScriptObject) plugin.GetProperty("content");
var action = (ScriptObject) content.GetProperty("Action");
action.Invoke("GetRandomNumber");
l

The final effect is the same. This way, however, you're making more round-trips between Silverlight and the underlying browser.

In a Nutshell

The Silverlight Base Class Library includes facilities to connect to the browser and the host page and to read information and access the DOM. If you have a Silverlight-centric application, you probably don't need to do much with the host HTML page. However, if yours is a mixed solution with both ASP.NET AJAX and Silverlight, then the need to execute managed code from JavaScript or invoke JavaScript objects from within managed code is a real one.

The browser interoperability layer (also known as the HTML bridge) contains a lot of functionality to enable communication between the managed world of Silverlight and the interpreted world of JavaScript. Communication requires marshalling of types and objects across the layers. At the highest level of abstraction, you find a public API and documentation to explain the few things you need to know to invoke managed code from JavaScript and JavaScript objects from managed code.

If you have a special issue, or just want to split hairs as far as implementation details are concerned, you might want to keep an eye on Wilco Bauwer's informative blog.

* This article originally appeared in the Cutting Edge section of MSDN Magazine.

Dino Esposito is an architect at IDesign and the coauthor of "Microsoft .NET: Architecting Applications for the Enterprise" (Microsoft Press, 2008). Based in Italy, Dino is a frequent speaker at industry events worldwide. You can join his blog at weblogs.asp.net/despos.

Previous Page: Inside the Browser Interoperability Layer  
Page 1: Configuring the Silverlight ControlPage 3: XAML, Managed Code, and JavaScript
Page 2: Inside the Browser Interoperability Layer 
Bytes by MSDN
Listen or watch influential community and Microsoft developers talk on topics they are passionate about.
Let's talk Windows Phone 7! Join our latest series of Bytes by MSDN as Tim Huckaby kicks it off with an interview with Brandon Watson, Director of Developer Experience for Windows Phone 7 at Microsoft.
Jim O'Neil explains how cloud computing can be a startup's best friend.
Cliff Simpkins and Brian Gorbett discuss the Windows Phone 7 developer experience and how they will make developers rich.
Whurley disucsses why developing on the Windows Phone 7 platform is enjoyable.
Chris Maliwat talks about how Internet Explorer 9 can improve site performance.
How Do I Videos