Prerequisites
- The Silverlight 3 Beta runtime. (note, if you still need to create Silverlight 2 applications, then you will want to install the Silverlight 3 Beta runtime in a separate environment. Check the official Silverlight.net site for more details)
- The Silverlight 3 Beta SDK
- The .NET 3.5 runtime
QuickStart
If you want to see the end result of this tutorial and you have installed all the prerequisites, then please download the ZIP file below, unzip it, deploy it to a web sever, and navigate to sloob.html in your browser.- Silverlight out-of-browser sample (you may read this software’s license here)
Step 1: Create the HTML & JS files
The HTML file, named sloob.html, will look the same as usual, but it now references the new Silverlight.js file found in the Silverlight 3.0 SDK Tools directory.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Silverlight Out-of-Browser Test</title>
<script type="text/javascript" src="Silverlight.js"></script>
<script type="text/javascript" src="createSilverlight.js"></script>
</head>
<body>
<div id="silverlightControlHost">
</div>
<script type="text/javascript">
// Find the div by id
var hostElement = document.getElementById("silverlightControlHost");
// Create the Silverlight control
createSilverlight(hostElement);
</script>
</body>
</html>
//creates the silverlight control within the tag specified by controlHostId
function createSilverlight( controlHost )
{
Silverlight.createObjectEx({
source: "ClientBin/SloobApplication.xap",
parentElement: controlHost,
id: "silverlightControl",
properties: {
width: "500",
height: "350",
version: "3.0.40307.0",
background: "white",
isWindowless: "true",
enableHtmlAccess: "true",
autoUpgrade: "false"
},
events: {}
});
}
Step 2: Create the XAML file
The XAML consists of several TextBlocks that display the launch location, running state and network status of the application, a modifiable network connection validity URL inside a TextBox and buttons to initiate a network status check and desktop install. The usage of these components will become clear as you read through the tutorial, but for now, create a file named “SloobControl.xaml” and put this code in it:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SloobApplication.SloobControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130" />
<ColumnDefinition Width="300" />
</Grid.ColumnDefinitions>
<TextBlock Text="Launch Location:" Width="110" Height="35" Grid.Column="0" Grid.Row="0" />
<TextBlock Text="Running State:" Width="110" Height="35" Grid.Column="0" Grid.Row="1" />
<TextBlock Text="Network Status:" Width="110" Height="35" Grid.Column="0" Grid.Row="2" />
<TextBlock Text="Network Status Url:" Width="110" Height="35" Grid.Column="0" Grid.Row="3" />
<TextBlock x:Name="LaunchLocation" Text="" Width="280" Height="35" Grid.Column="1" Grid.Row="0" />
<TextBlock x:Name="RunningState" Text="" Width="280" Height="35" Grid.Column="1" Grid.Row="1" />
<TextBlock x:Name="NetworkStatus" Text="" Width="280" Height="35" Grid.Column="1" Grid.Row="2" />
<TextBox x:Name="NetworkStatusUrl" Text="" Width="280" Height="25" Grid.Column="1" Grid.Row="3" />
<Button x:Name="CheckNetworkStatus" Content="Check Network Status" Click="OnCheckNetworkStatusClicked" Width="280" Height="25" Grid.Column="1" Grid.Row="4" />
<Button x:Name="Install" Content="Install" Click="OnInstallClicked" Width="280" Height="25" Grid.Column="1" Grid.Row="5" />
</Grid>
</UserControl>
Step 3: Add location detection logic
We start with a class named SloobControl in “SloobControl.xaml.cs”:
namespace SloobApplication
{
using System;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Xml;
using System.Xml.Linq;
public partial class SloobControl : UserControl
{
public SloobControl()
{
InitializeComponent();
}
}
}
private void DetectLaunchLocation()
{
if( Application.Current.ExecutionState == ExecutionStates.RunningOnline )
{
LaunchLocation.Text = "Launched in a browser.";
}
else if( Application.Current.RunningOffline )
{
LaunchLocation.Text = "Launched from the desktop.";
}
else if( Application.Current.ExecutionState == ExecutionStates.Detached )
{
LaunchLocation.Text = "Launched in browser.";
}
}
We should detect the launch location on load, so let’s add a call to DetectLocation in the constructor:
public SloobControl()
{
InitializeComponent();
DetectLaunchLocation();
}
Step 4: Add running state logic
Similar to the previous step, let’s add a function called DetectRunningState:
public partial class SloobControl : UserControl
{
private bool detaching = false;
public SloobControl()
{
...
}
private void DetectRunningState()
{
if( Application.Current.ExecutionState == ExecutionStates.RunningOnline )
{
RunningState.Text = "Running in browser.";
}
else if( Application.Current.ExecutionState == ExecutionStates.Detaching )
{
this.detaching = true;
RunningState.Text = "Detaching from browser.";
}
else if( Application.Current.ExecutionState == ExecutionStates.Detached )
{
if( this.detaching )
{
this.detaching = false;
RunningState.Text += "...complete.";
}
else
RunningState.Text = "Running from cache.";
}
else if( Application.Current.ExecutionState == ExecutionStates.DetachedUpdatesAvailable )
RunningState.Text = "Running from cache but updates are available.";
else if( Application.Current.ExecutionState == ExecutionStates.DetachFailed )
RunningState.Text = "Unable to detached from browser.";
}
According to the Silverlight out-of-browser documentation you can prevent an application from automatically upgrading itself by editing the registry.
If the execution state changes over time, that means Silverlight must send us a notification when a change occurs. This happens when the Silverlight runtime raises the Application.Current.ExecutionStateChanged event. So, we should add code to respond to this event, like so:
public partial class SloobControl : UserControl
{
private bool detaching = false;
private bool checkingNetwork = true;
public SloobControl()
{
InitializeComponent();
Application.Current.ExecutionStateChanged += OnExecutionStateChanged;
DetectLaunchLocation();
DetectRunningState();
}
void OnExecutionStateChanged(object sender, EventArgs e)
{
DetectRunningState();
}
Now for the hard part.
Step 5: Add network status logic
As Peter Smith noted in his Silverlight presentation at Mix ‘09 to insure that your Silverlight application has access to the Internet you should actually attempt to download and verify bits. He also wrote two articles for the Network Class Library Team blog for general online/offline detection in .NET and different code patterns to use for offline detection in Silverlight. For simplicity, I will only implement a bare bones method here, so I urge anyone needing to implement this feature to read both of these posts.Like usual, let’s start with a function named DetectNetworkStatus:
public partial class SloobControl : UserControl
{
private const string defaultUrl = "http://www.dieajax.com/downloads/tutorial/silverlight/sloob/test.xml";
private WebClient webClient = new WebClient();
private bool checkingNetwork = true;
public SloobControl()
{
InitializeComponent();
NetworkStatusUrl.Text = defaultUrl;
...
}
private void DetectNetworkStatus()
{
bool networkAvailable = NetworkInterface.GetIsNetworkAvailable();
if( networkAvailable )
{
NetworkStatus.Text = "Determining network status...";
string networkStatusUrl = NetworkStatusUrl.Text + "?FoolCache=" + DateTime.Now.ToString();
webClient.DownloadStringAsync(new Uri(networkStatusUrl , UriKind.Absolute));
this.checkingNetwork = true;
CheckNetworkStatus.Content = "Cancel Network Status Check";
}
else
this.ShowNetworkStatus( false, null );
}
private void ShowNetworkStatus( bool online, String additionalInfo )
{
if( online )
NetworkStatus.Text = "Connected to network.";
else
NetworkStatus.Text = "Disconnected from network.";
if( additionalInfo != null )
NetworkStatus.Text += "\n" + additionalInfo;
}
Note, in this example, per Peter Smith’s suggestion, I download and validate an XML file to verify that I have network connectivity. However, you can download any type of resource you want, just as long as it contains data you can verify.
You may have noticed the “FoolCache” get variable appended to the URL before the call to DownloadStringAsync. Like checking the XAP file for updates, for efficiency’s sake, the browser sets the If-Modified-Since header when doing the HTTP call to the URL. If the file hasn’t changed, the browser just pulls the file from its cache and hands it to Silverlight. However, it appears my web server doesn’t respect that, meaning that even if I change the file on the web server, the browser, and thus Silverlight, will continue to pull the file from the cache. As recommened by Microsoft employee, I attempted to manually set the If-Modified-Since HTTP header to a much older value, forcing the browser to always think a newer file exists on the server, but I got a “restricted header” exception from the WebClient class. Finally, I decided to use a unique URL every time via the “FoolCache” HTTP GET parameter which, while being more hack-ish, also fools the browser into always getting the file. A few others have implemented a similar solution:I should also note that I couldn’t get the XAP file auto-update working either because it uses the same mechanism. In the future, I hope Microsoft will give developers an easy way to customize when to update an installed Silverlight application.
Finally, the ShowNetworkStatus function simply wraps some common display logic.
When the WebClient finishes downloading or cancels a download, it raises an event that we should handle:
public SloobControl()
{
...
webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(OnDownloadStringCompleted);
...
}
private void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if( !e.Cancelled )
{
if( e.Error == null )
{
XDocument doc = XDocument.Parse( e.Result );
if( (doc != null ) && (doc.Element( "test" ) != null) )
this.ShowNetworkStatus( true, null );
else
this.ShowNetworkStatus( false, "Error retreiving verification data." );
}
else
{
this.ShowNetworkStatus( false, "Error contacting verification site." );
}
}
else
NetworkStatus.Text = "Cancelled.";
this.checkingNetwork = false;
CheckNetworkStatus.Content = "Check Network Status";
}
- Learn more about the NetworkInterface.GetIsNetworkAvailable function
- Learn more about the WebClient class
- Documentation mentioning that certain HTTP headers are restricted
- Documentation defining some of the restricted HTTP headers
- Documentation defining HTTP headers that the browser controls
Step 6: Trigger network status checks
This application has three scenarios for when it should check network status:- When the OS detectes a network address change
- On application start
- When the user clicks on the Check Network Status button
public SloobControl()
{
...
NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged;
...
DetectNetworkStatus();
}
void OnNetworkAddressChanged( object sender, EventArgs args )
{
if( !this.checkingNetwork )
DetectNetworkStatus();
}
void OnCheckNetworkStatusClicked( object sender, RoutedEventArgs args )
{
if( this.checkingNetwork )
{
webClient.CancelAsync();
}
else
{
DetectNetworkStatus();
}
}
Chris Koeing mentions in his blog that the NetworkChange.NetworkAddressChanged event appears “chatty” and fires multiple times for what may look like a single network change, so try not to do too much work when handling this event.
Step 7: Programmatically trigger an install
Of course, the user can always install any Silverlight application that has out-of-browser support by right-clicking on the application and selecting the “Install xxxx onto this computer” menu item, where “xxxx” represents the name of the applicaiton. However, Microsoft also provided a way to install applications programmatically. I should note that application install can only occur as a result of a user action, for example, a button press. Trying to install an application on the constructor won’t work. With that in mind, I added a button named “Install” in the XAML with a click event handler function named OnInstallClicked. The logic for that function looks like this:
void OnInstallClicked( object sender, RoutedEventArgs args )
{
Application.Current.Detach();
}
To uninstall a Silverlight application, you can right-click on the application after you launch it from either the desktop or the browser and select “Uninstall xxxx from this computer”.
Step 8: Build it
The sample code bundle contains the project file and all the support files needed to build this sample. The build file looks similar to the build file in my Silverlight MSBuild tutorial with the exception of a few changes to the project file and application manifest:SloobApplication.csproj
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="system" />
<Reference Include="system.Core" />
<Reference Include="System.Windows" />
<Reference Include="System.Net" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Windows.Browser" />
</ItemGroup>
.…
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight\v3.0\Microsoft.Silverlight.CSharp.targets" />
AppManifest.xml
<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Deployment.ApplicationIdentity>
<ApplicationIdentity
ShortName="SLOOB Test"
Title="Silverlight 3 Out-Of-Browser Test">
<ApplicationIdentity.Blurb>
Tests out-of-browser functionality in Silverlight 3
</ApplicationIdentity.Blurb>
</ApplicationIdentity>
</Deployment.ApplicationIdentity>
</Deployment>
You can build the application with this command:
"C:\WINDOWS\Microsoft.NET\Framework\v3.5\msbuild.exe" SloobApplication.csproj
Step 9: Run it
Normally, I test my Silverlight applications by simply opening the HTML file locally, but for this tutorial, XAP update checks and network status checks won’t work if you run it locally. Go ahead and play around with installing, launching and removing the application and watch how the text statuses change. For me, it remained very unintuitive at first, however, now I pretty much have a handle on how Silverlight works with cached/installed applications…and it almost kinda makes sense.Conclusion
So, that’s Silverlight out-of-browser support. To be honest, it feels a little last minute to me. Or perhaps Microsoft simply decided not to over reach and do the bare minimum to have feature parity with JavaFX. Although, I must admit my disappointment at not seeing any cool “drag to install” (no pun intended ) demos like I’ve seen for JavaFX. I assume Silverlight 3 won’t support functionality like this upon release, but hopefully it’ll come in a later version.I did however discover something very promising while perusing the new Silverlight doucmentation: it looks like Microsoft finally realizes that people want to host Silverlight outside of the browser and will officially support this (to an extent). The Mono guys have focused on Silverlight “desklets” from day one, so I makes me happy to see Microsoft finally moving towards doing the same. Silverlight out-of-browser support definitely feels like a tacked-on, “checkbox” feature this release, but I remain excitied to see how it evolves in the future.
No comments:
Post a Comment