A headless Silverlight application is a Silverlight application that contains no user interface. The obvious question is why, why on earth would you ever want to create a web site with a Silverlight control that has no UI? I can think of good reason. What if you want to make a cross domain call. Traditionally, this was done by making a call back to the same domain server. The server would make the requested call on behalf of the client and return the data back to the client. Wouldn't it be great if the client could make the call directly.
Silverlight has the capability of making cross domain calls provided the server that hosts the service has been given access via the either the ClientAccessPolicy.xml or crossdomain.xml file. This post will describe how to use a headless Silverlight control to simplify cross domain calls from the client browser. I will not spend too much time discussing how to create a WCF service but rather will use an existing WCF service. The reader is directed to a previous post Calling a WCF Service from Silverlight 2.0: part Two. Likewise, I will not be discussing, in any great detail, how interoperability between Silverlight and the HTML DOM is done. Again the reader is encouraged to read a prior post How to communicate between Silverlight controls.
Let's start writing this headless Silverlight application.
Using VS2008, create a new Silverlight application. I decided to call the project HeadlessSilverlight. I like to use jQuery and if you want to follow this tutorial, as is, then I recommend you add jQuery.js to the web project. You can download jQuery from jquery.com. I also like to remove the generated default.apsx file and rename the generated TestPages to default. So the HeadlessSilverlightTestPage.aspx is renamed to default.apsx and the HeadlessSilverlightTestPage.html to default.html. After doing all this you should have a solution similar to Figure 1.
Figure 1: Initial Solution
The next step to is alter the Silverlight control so that it has no height or width. Edit the default.aspx and make the following changes:
- Remove the width and height in the Silverlight control.
- Remove the style attribute from the div element that hosts the Silverlight control.
Listing 1 contains the altered markup.
Listing 1: The Silverlight control with no height or width.
41 <div>
42 <asp:Silverlight
43 ID="Xaml1" runat="server"
44 Source="~/ClientBin/HeadlessSilverlight.xap"
45 MinimumVersion="2.0.31005.0" />
46 </div>
Similarly the XAML needs to be modified. The modifications are shown in Listing 2.
Listing 2: headless Silverlight XAML markup.
1 <UserControl x:Class="HeadlessSilverlight.Page"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 >
5 <Grid x:Name="LayoutRoot"></Grid>
6 </UserControl>
Of note both the grid and the user control have no width or height.
We need some way to initiate the cross domain service call. Modify the default.apsx markup to include a button. When the user clicks this button we will call the service but rather than use javascript to directly call the service we will use Silverlight to accomplish the task. Listing 3 contains the markup for the button. Place this after the markup for the Silverlight control.
Listing 3: HTML Button
47 <div>
48 <input
49 id="btnCrossDomain" type="button"
50 value="Call Cross Domain Service" />
51 </div>
Notice that we have not wired up the click event in markup and this is where jQuery shines. jQuery makes it easy to decouple the UI from behaviour. Enough said let's see how the click is wired up.
Listing 4 contains the javascript call to jQuery that wires up the click event to an anonymous function (sometimes called inline function).
Listing 4: Supporting javascript code.
20 <script type="text/javascript" src="jquery.js"></script>
21
22 <script type="text/javascript">
23 // Use jQuery to attach a function to the button click event
24 $(function() {
25 $('#btnCrossDomain')[0].onclick = function(event) {
26 //call service via silverlight
27 var host = document.getElementById("Xaml1");
28 host.content.SL_Headless.CallService();
29 }
30 });
31 // The Silverlight managed code will call this javascript function
32 // when it has made the service call.
33 function displayResponse(response) {
34 alert(response);
35 }
36 </script>
First we need to include the jQuery library. This is done at line 20. Lines 24 to 30 is basically how we wire up the click event of the button with id #btnCrossDomain with an anonymous function. This anonymous function makes the call into the Silverlight managed environment and ultimately makes the service call. More on that later. The function displayResponse will be called from the Silverlight managed environment and will display the data from the service call. More on that later.
Let's examine more closely how the call to Silverlight is made. Line 27 is used to retrieve the Silverlight plug-in instance. This instance exposes a content property from which you can access a scriptable object. Only managed instances that have been explicitly registered are accessible from javascript. Once we have a reference to the scriptable object then any method that has been decorated with the ScriptableMemberAttribute can be called. This can be a little confusing and will become clearer when we investigate the Silverlight code.
But before we can add the code that actually calls the WCF service, we need to add a service reference to the Silverlight project. Select the Silverlight project and press the right mouse button and select the Add Service Reference menu option. This will bring up the Add Service reference dialog. I have decided to use a service I created for an earlier post. You are free to use this service or any other but remember if you chose a third party service then make sure that they have granted access to the service via the ClientAccessPolicy.xml file. Type the following in the Address field of the dialog http://www.cynotwhynot.com/services/people/PersonService.svc and then select Go. After a bit of time the Services listbox should contain a service named PersonService. Lastly, change the namespace to PersonProxy. Figure 2 shows this dialog.
Figure 2: Adding a reference to the WCF service.
Selecting the OK button will add the proxy to the Silverlight project. This service contains a single method, GetPeopleAsync and is used to get a list of Person objects.
With this reference in place we can concentrate on the code behind for the headless Silverlight control. Listing 5 contains the code for Page.xaml.vb
Listing 5: The Page.xaml.vb code behind.
1
2 Imports System.Windows.Browser
3 Imports System.Collections.ObjectModel
4
5 Partial Public Class Page
6 Inherits UserControl
7
8 Public Sub New()
9 InitializeComponent()
10 End Sub
11
12 <ScriptableMember()> _
13 Public Sub CallService()
14 Dim proxy As New PersonProxy.PersonServiceClient()
15 AddHandler proxy.GetPeopleCompleted, AddressOf onGetPeopleCompleted
16 proxy.GetPeopleAsync()
17 End Sub
18
19 Public Sub onGetPeopleCompleted( _
20 ByVal sender As Object, _
21 ByVal e As PersonProxy.GetPeopleCompletedEventArgs)
22
23 Dim response As String
24 If e.Error Is Nothing Then
25 response = String.Format( _
26 "Cool a cross domain call. {0} {1}", _
27 e.Result(0).First, e.Result(0).Last)
28 Else
29 response = String.Format( _
30 "Cool a cross domain call but an error occurred. {0}", _
31 e.Error.ToString)
32 End If
33
34 'Call Javascript to display the response
35 Dim displayResponse As ScriptObject = CType(HtmlPage.Window.GetProperty("displayResponse"), ScriptObject)
36 displayResponse.InvokeSelf(response)
37
38 End Sub
39
40
41 Private Sub Page_Loaded( _
42 ByVal sender As Object, _
43 ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
44 HtmlPage.RegisterScriptableObject("SL_Headless", Me)
45 End Sub
46
47 End Class
Lines 12 through 17 contains the code that makes the call to the WCF service. The ScriptableMemberAttribute on line 12 and the call to RegisterScriptableObject on line 44 exposes the CallService method to javascript. The call to the WCF service is made within the CallService method. The Silverlight model enforces that all out of band calls must be made asynchronously and therefore the response must be processed within a callback, OnGetPeopleCompleted. This callback takes the return data (e.Results) and packages it into a string. For simplicity only the first person in the list is included in the response string. If a problem occurs when calling the service, the error is placed in the response string. Finally, lines 35 and 36 call the javascript function, displayResponse and if you recall this function calls alert, displaying the response string .
Figure 3 shows the alert containing the data from the WCF service call. Notice that the alert contains the person John Smith. That's it a cross domain call from within the client browser.
Figure 3: The headless Silverlight application running in a browser.
Without your space helmet, Dave, you're going to find that rather difficult.