Inside the Browser Interoperability Layer
A graphical view of the Silverlight browser interoperability layer and access to the page DOM is shown in Figure 4. Any requests made to any classes in the interoperability layer are resolved through an internal browser host service. Information is marshaled down to the browser's unmanaged environment and then back to Silverlight. Type differences are hidden and taken care of by the interoperability layer. DOM-level objects are wrapped as managed objects and served to the Silverlight code through a new managed interface—HtmlDocument, HtmlWindow, and the like. Let's focus on the GetElementById method.
 | |
| Figure 1. The HTML Bridge in Silverlight 2 |
As its first step, the method ensures that the code is being called on the Silverlight UI thread. If not, an exception is thrown. Next, a request is made to the underlying browser to get a reference to the specified DOM element. If the request is successful, the method gets an unmanaged reference for the DOM object for which it will create and return a managed wrapper.
Attaching Managed Code to DOM Events
A very nice result of the Silverlight and DOM interaction is the ability you're given to run managed code in response to DOM events. For example, when the user clicks on a button, you can execute C# code instead of JavaScript code. Here's how you can achieve that:
HtmlElement button1;
button1 = HtmlPage.Document.GetElementById("Button1");
button1.AttachEvent("click",
new System.EventHandler(Button1_Click));
You first retrieve a managed reference to the button (or the DOM element) of interest. Next, you invoke the managed AttachEvent method to register a handler for the specific event. The really nice thing is that the handler is managed code whereas the event is triggered at the browser unmanaged level. For example, getting a GUID is nearly impossible in JavaScript. It becomes fairly easy, however, if you can rely on the power of managed code through Silverlight:
void Button1_Click(object sender, EventArgs e)
{
// Get a new GUID
Guid g = Guid.NewGuid();
// Display the GUID in the page user interface
HtmlElement label1 = HtmlPage.Document.GetElementById("Label1");
label1.SetProperty("innerHTML", g.ToString());
}
Needless to say, the managed code is a member of the Silverlight page codebehind class. The effect of the operation can be reflected both in the HTML of the host page (as in the preceding example) or directly in the Silverlight user interface—it is entirely up to you and your circumstances.
The attachment of the event is an operation that occurs through the browser interoperability layer and ends up calling the AttachEvent method on DOM objects. When the browser triggers the page-level event, a call is made back to Silverlight to execute the managed code.
When is such a feature helpful in the real world? Silverlight is a product designed to serve up a rich Web front end. You need Silverlight if you need managed code in the browser to do things that a managed language does better and faster than script. Examples of this are code-intensive operations where the speed of a compiled language beats script hands down, or operations that are not available in a functionally limited environment like the browser's. For sure, you don't strictly need Silverlight if all that you do is manipulate the DOM. So the ability to handle events in managed code is a very good feature to have, but it does require Silverlight. And you probably wouldn't want to engage Silverlight only for handling events.
In Silverlight 2, the HtmlWindow object provides the managed representation of the JavaScript window object. It stores a reference to the DOM object and allows you to drive it with a set of managed methods: Alert, Confirm, Prompt, Submit, Navigate, and even Eval. An instance of the HtmlWindow object is exposed to Silverlight developers through the Window property of the HtmlPage class. The following shows how to display a browser's message box from Silverlight:
HtmlPage.Window.Alert("Hello, world");
Let's explore what happens under the hood of this simple piece of code. The model I'll examine repeats itself in almost all methods on the HtmlWindow class:
public void Alert(string message)
{
HtmlPage.VerifyThread();
if (message == null)
{
message = string.Empty;
}
this.Invoke("alert",
new object[] { message });
}
After the test on the UI thread, the method fixes the message string if it is null and proceeds to call the Invoke method on the base ScriptObject class. The Invoke method is the bridge between the managed world of Silverlight and the browser.
The method accepts two parameters, as you see here:
public virtual object Invoke(string name, params object[] args)
The first argument indicates the name of the method to invoke on the scriptable object stored in the current instance of ScriptObject. The second argument is simply the list of arguments for the method to invoke.
The method is responsible for correctly marshaling types across the two different runtime environments. On the way to the browser, it transforms managed objects into JavaScript-compatible types; on the way back, it does the reverse.
There's another method on HtmlWindow that deserves some attention here—the CreateInstance method:
public ScriptObject CreateInstance(
string typeName,
params object[] args)
The method allows you to create an instance of the specified JavaScript object. The type name parameter indicates the name of the JavaScript object to instantiate. Internally, the method prepares a dynamic JavaScript function that creates the specified object and then invokes the function from Silverlight:
ScriptObject xhr = HtmlPage.Window.CreateInstance("XMLHttpRequest");
The method CreateInstance is useful for obtaining a scriptable reference to a JavaScript object. Next, you script it using the Invoke method. These features are incredibly helpful when you really need to make synchronous calls from Silverlight 2. The problem is that synchronous calls are not permitted in Silverlight 2.
Synchronous Calls and Silverlight 2
Silverlight 2 supplies a variety of APIs to make calls to remote endpoints, but all of them must be asynchronous. You can start the call on a background thread, but you can't force the UI thread to sync up. If you block the UI thread on a sync object, you actually stop the UI thread indefinitely, and no command that resets the sync object can ever resume it. There's a big debate in the community about synchronous calls in Silverlight.
Nonetheless, synchronous calls from within the Silverlight API to a remote endpoint are simply not supported today because they are known and proven to have associated latency. Synchronous calls are an important feature in the browser environment, however. They're supported in all browsers that support XmlHttpRequest.
XmlHttpRequest is a browser object that enables AJAX scenarios. The object leverages the browser machinery to make a call to an endpoint within the same domain. By default, XmlHttpRequest operates asynchronously, and this is the way in which most AJAX frameworks use it. However, XmlHttpRequest can be quite easily configured to operate synchronously.
By creating and controlling an instance of XmlHttpRequest from Silverlight, you can arrange synchronous calls and fix all those particular scenarios where a synchronous call would make coding much easier. (Personally, I'm not a big fan of the async-only nature of Silverlight remote calls. Async calls are sufficient most of the time, but I think that, especially when you're adapting some existing front end to Silverlight, you may run into situations where a synchronous call would save you a lot of redesign. I'm the first to say that design is key, but if a consciously written synchronous call can save me hours or days of work, I'll definitely go for it.)
That said, the Silverlight team has good reasons to push the async-only approach because synchronous calls to remote endpoints could likely cause Silverlight applications to freeze the user interface, thus deteriorating the end user's experience with his browser and Web applications. Synchronous calls are technically possible, but the team does not support this natively in the platform in the interest of all applications and their consumers. Likewise, the team does not recommend resorting to a manually written synchronous remote call using XmlHttpRequest, as in Figure 5.
Figure 5 Making Synchronous Calls from within Silverlight 2
private void Button1_Click(object sender,
System.Windows.RoutedEventArgs e)
{
string url = "...";
ScriptObject xhr = HtmlPage.Window.CreateInstance("XMLHttpRequest");
xhr.Invoke("open", "POST", url, false);
xhr.Invoke("setRequestHeader", "Content-Type",
"application/x-www-form-urlencoded");
// Prepare the body as the endpoint expects it to be
string body = "...";
xhr.Invoke("send", body);
string response = (string) xhr.GetProperty("responseText");
// Process the response and update the UI
ProcessData(response)
}
Figure 5 shows how to use XmlHttpRequest to set up a synchronous same-domain call to a URL from within Silverlight 2. The key statement is when you invoke the open method on XmlHttpRequest. The Boolean argument indicates whether the call has to be asynchronous. If false, you instruct the object to proceed in a synchronous manner.
The drawback of this trick is that it leverages a low-level tool that just doesn't offer any facilities for converting strings to and from, say, JavaScript Object Notation (JSON) streams. If you employ this trick, you have to take care of any JSON serialization and deserialization using the DataContractJsonSerializer class.