Deep linking into Silverlight applications

This article was originally published in VSJ, which is now part of Developer Fusion.
Silverlight 2 is undoubtedly a very exciting Rich Internet Application platform for the .NET developer. However, it does have one considerable disadvantage when compared to the traditional page-based model of the Web: it has no inherent support for bookmarking the state of a Silverlight application, or for using the browser’s back and forward buttons to navigate within the Silverlight application. There is also the risk that the entire state of a Silverlight application can be lost by merely refreshing its host page, a frustrating moment when you’re thirty-nine steps through a Silverlight wizard.

So in this article you’ll learn how to use Silverlight’s HTML Bridge, in conjunction with some popular Ajax libraries, to achieve both deep linking and integration with the browser history function for your Silverlight applications.

And rather than deal with yet another shopping cart or photo/media catalogue, you’ll get to see how to do this work in one of the finest games ever invented, Tic-Tac-Toe, as it exhibits all of the requirements for more complex applications, but presents them in a small, digestible package.

Stateless Tic-Tac-Toe

Figure 1 shows a simple Tic-Tac-Toe application that’s been written in Silverlight 2. The user can click the buttons in turn. The application remembers the state of the game and updates the UI with a corresponding X or O.

Figure 1
Figure 1: Silverlight Tic-Tac-Toe

Now consider the sequence of moves that might occur during a game, as shown in Figure 2.

Figure 2
Figure 2: No support for navigation

It would be nice to address two problems with the game as it currently stands. The first is a simple one – allowing the user to bookmark the state of the game, so that they can either email a URL to a friend to ask them for advice (or to show off their prowess). The second is also relatively straightforward – allowing the user to undo a move by clicking the browser’s back button, and then redo the move by clicking the forward button.

We’ll tackle these two problems, starting with the first.

Deep linking into a Silverlight application

In the traditional Web model, there is a one to one correlation between a page and its URL. Linking to a page is therefore straightforward. With Silverlight (or any other RIA technology for that matter), a single Web page hosts the application, and any navigation within that application is not typically reflected in the URL. The term “deep linking” has several meanings, but in the case of a Silverlight application it tends to mean the ability to link to a state within the Silverlight application. So considering Figure 2 as our example, the application has three four states, starting with the original empty state and with a new state being reached after each move is made.

Clearly, the state could be passed using a query string variable, such as:

http://.../TicTacToePage.html?State=254
This would easily allow the user to copy and paste this URL as they desire, and Silverlight can trivially reach into the original query string variables.

However, this is not the normal strategy that is adopted. Instead, it is much more prevalent to see Silverlight applications exploit the standard bookmarking feature of a URL, whereby content is placed after a # symbol in the URL. This is demonstrated in Figure 3, where the three opening moves are stored using a bookmark within the current page.

Figure 3
Figure 3: Bookmarking the moves

The difference between a query string and a bookmark might appear to be subtle (i.e. a # symbol instead of a question mark), but the core difference is that the browser will not perform a round-trip to the server when navigation to a bookmark is made within a page, whereas it will round-trip when a request is made to navigate to the same page with a different query string.

Bookmarks and the Tic-Tac-Toe game

If you ever need to implement Tic-Tac-Toe in a computer program – and let’s face it all good applications typically need such a feature – then a magic square is likely to be your friend, as it is by far the simplest way to do things like checking for a winning position; e.g. all you have to do is determine whether a player owns any three cells whose values add up to 15. Figure 4 highlights the state of the board after the initial three moves, along with the magic square that is used to represent the game.

Figure 4
Figure 4: The magic square

Thus the bookmark “#254” represents the X player, who always moves first, taking the top left square (2), followed by the O player taking the centre square (5) with the X player then taking the top right square (4).

Accessing the bookmark data

Silverlight can reach out to identify the bookmark data using its HTML Bridge. In this specific case, you would use the following code to read the bookmark data:
string moveData =
	HtmlPage.Window.CurrentBookmark;
This makes it very easy to set the state of your application when the page is loaded, simply by reading the value of the CurrentBookmark, as shown below:
private bool _isAutoLoading = false;
void Page_Loaded(object sender,
	RoutedEventArgs e)
{
	string moveData =
		HtmlPage.Window.CurrentBookmark;
	if (!String.IsNullOrEmpty(moveData))
	{
		foreach (char c in moveData)
		{
			MakeMove( c );
		}
	}
}

void MakeMove( char selectedCell )
{
/ Record the move and update the UI
...
}

void HandleCellClick( object sender,
	EventArgs e )
{
	char cell = GetCellForButton(
		sender as Button );
	MakeMove( cell );
	HtmlPage.Window.CurrentBookmark =
HtmlPage.Window.CurrentBookmark + cell;
}
In this example, each individual character of the bookmark represents a move, so the application simply gets the game to replay the moves from the beginning (e.g. select cell 2, then 5, then 4).

Similarly, each time a player makes a move the information is recorded by appending the selected cell to the CurrentBookmark property. In this example, the bookmark is only updated in response to a user action.

As an alternative to setting the CurrentBookmark property, you can use the HtmlPage.Window.NavigateToBookmark() method to force the browser to navigate within a page to the new bookmark. Whilst this will update the bookmark in the URL, it will not cause your Silverlight application to restart.

Can it really be that easy?

As you can see, implementing the basic URL aspects of deep linking within a Silverlight application can be remarkably straightforward. However, there are a couple of things to consider before you get too excited.

The first thing to ponder is how you might restore state in a more complex scenario. In the case of the Tic-Tac-Toe game, it is easy enough to simply replay the moves by reading them directly from the bookmark, as shown in the above listing. For more complex scenarios, such as in a Silverlight application for filling in a Tax Return, or even for the proverbial shopping cart, the state would need to be stored and reloaded from the server, in which case you might choose to use the bookmark as a key into the server store. Note that using IsolatedStorage for this data would defeat the point of allowing the user to bookmark or email a link, as isolated storage is located on a specific client.

Clearly, if you do use a bookmark as a key then you must ensure that you implement an appropriate security infrastructure to ensure that the keys are not guessable (i.e. you’re not using sequential numbers) and that appropriate authentication/authorisation is performed before you re-populate the state on the client.

A bridge too far

Another issue that you must take into account is that Silverlight can only reach out to the HTML hosting page if, and only if, the HTML Bridge is enabled. This is controlled through the enableHtmlAccess parameter that you specify for your Silverlight plug-in element, as shown below:
<object
data="data:application/x-silverlight-2,"
	type="application/x-silverlight-2"
	width="100%" height="100%">
	<param name="source"
	value="ClientBin/DeepTicTacToe.xap"/>
	<param name="enableHtmlAccess"
		value="true" />
		...
</object>
If this parameter’s value is set to false, then no access to the HTML DOM is permitted. Thus, any attempt to access the bookmark via the HtmlPage.Window.CurrentBookmark – or any other use of HtmlPage and its related types – is doomed to fail with an InvalidOperationException. Consequently, any attempt at deep linking into your Silverlight application will also fail.

In general, you should always check the status of the bridge using the HtmlPage.IsEnabled property before attempting to use it.

One final note: if you’re using the ASP.NET Silverlight server-side control, rather than the <object> element directly, then this parameter is set using its HtmlAccess property. To turn it on, use either:

HtmlAccess="Enabled"
…or:
HtmlAccess="SameDomain";

Back/forward navigation support

Working with the HtmlWindow’s CurrentBookmark property enables you to create an application that can potentially manage its state, thus allowing users to deep link into specific parts of the application. However, by itself it can’t solve the second problem described at the beginning of this article, namely using the Back and Forward buttons in the browser to navigate through the application.

This is because setting the CurrentBookmark property (or calling NavigateToBookmark()) doesn’t insert an entry into the browser’s history list, thus making the Back and Forward buttons redundant and precluding you from trivially calling out to the JavaScript history object’s go(), forward() and back() methods to achieve programmatic navigation.

The most common approach that’s been taken to solve this problem is to introduce a hidden <iframe> element onto the hosting page, and then to use some JavaScript bridging code to perform navigation within the frame. When an <iframe> navigates, the browser will record this fact in the history list. You can then handle the relevant navigation event from the browser, by hooking it and calling a method on a scriptable Silverlight object that you’ve exposed via the HTML Bridge.

However, this is a whole chunk of work that is fraught with browser compatibility issues; and one of the joys of Silverlight is the fact that you have to spend far less time worrying about the browser than when writing a “pure” Ajax application.

Fortunately, this being the Web, someone else has already done most of the hard work in implementing a RIA navigation solution for you. The ASP.NET team introduced history and navigation support for ASP.NET AJAX in 3.5 SP1, so as a Silverlight developer you can simply piggyback onto their implementation and let their code take all the strain.

Let’s take a look at how easy this is to achieve, by using ASP.NET AJAX’s ScriptManager to implement an undo/redo mechanism for Tic-Tac-Toe application.

Using the ScriptManager

A common starting point for implementing history support is to use the ASP.NET ScriptManager to do the heavy lifting. This is as easy as adding the following to your hosting ASP.NET page:
<asp:ScriptManager ID="ScriptManager1"
	EnableHistory="true" runat="server" />
Note that you must set the EnableHistory property to true, which is not its default state. If you forget to do this, exceptions will soon follow.

With this in place you can now call the Sys.Application.addHistoryPoint() method from within your Silverlight application to add items into the browser’s history list. Note the casing: you’re dealing with the fun of JavaScript now. Typically, you’ll wrap this call inside a managed wrapper class, as shown below:

public class HistoryNavigator
{
	public static void AddHistoryPoint(
string title, string key, string value )
	{
		string script =
			"Sys.Application.addHistoryPoint(
			{{ {1}:'{2}' }}, '{0}' );";
		HtmlPage.Window.Eval(
			string.Format(
			script, title, key, value));
	}
// rest of class elided
...
}
The addHistoryPoint() method requires you to pass a JSON object, which in this case is just a simple name/value pair, along with a title that will appear in the history list. Out of the box, the name/value pair will be written as a bookmark within the page using the format #name=value, making it trivial for the user to add actually bookmark the page if they so desire.

The implementation above uses the HtmlWindow.Eval() method to evaluate and execute the JavaScript. As you can see, there is inherent joy in mixing .NET string formatting and JSON syntax, so just to make it absolutely clear, the following C# code:

HistoryNavigator.AddHistoryPoint(
	"X player took the top left cell",
	"move", "2" );
…will yield the following JavaScript that gets evaluated and executed:
Sys.Application.addHistoryPoint(
	{ move : '2' },
	'X player took the top left cell' );
Figure 5 shows the result of calling addHistoryPoint() after making three moves.

You can see the URL has been updated with the move=254 bookmark, just as if you had used CurrentBookmark. Of course, you no longer need to use CurrentBookmark or even NavigateToBookmark(). As you can see from Figure 5, the main advantage of using addHistoryPoint() is that the browser’s history list is now populated with each move that has been made, thus allowing the user to navigate using the Back and Forward buttons.

Figure 5
Figure 5: Tic-Tac-Toe with history

So how does addHistoryPoint() work?

A quick look at the page’s source, as sent to the browser, reveals the presence of a hidden <iframe> element, as shown below:
<iframe id="__historyFrame"
	src="/ScriptResource.axd?d=
L5HpcNDqQFliCZysHBIoEKrpgc_yvNILi25wZDqTxOg1"
	style="display:none;">
</iframe>
This should come as no surprise: it is, after all, a fairly standard way to “fake out” the browser’s history mechanism.

Now it’s perfectly possible that you don’t want to, or simply can’t, use ASP.NET’s ScriptManager server-side control. That’s absolutely fine. The ASP.NET AJAX Library can be downloaded and used in its source form, so that it can be used with many different server technologies. Similarly, there are many other Ajax libraries that offer history support, including popular libraries such as jquery and its history plug-in. All work in broadly the same manner, so in the case of jquery you could use the following code in place of the ASP.NET AJAX library’s Sys.Application.addHistoryPoint():

public class HistoryNavigator
{
	static HistoryNavigator
	{
// Required to initialise the
// jquery history functionality
// Placing this in the .cctor ensures
// that it is called prior to
// any use of the AddHistoryPoint()
// method
		HtmlPage.Window.Eval(
			"$.history.init(
			function(hash) {} );"
	}
// Unchanged
	public static void AddHistoryPoint(
		string title,
		string key,
		string value )
	{
		HtmlPage.Window.Eval(string.Format(
			"$.history.load('{0}');", value));
	}
// rest of class elided
		...
}

Handling the navigate event

At this point, the application correctly updates the history list. However, it doesn’t respond to the user clicking either the Forward or Back buttons. For that, you need to handle the navigate event. Again, ASP.NET AJAX (or jquery et al) provide easy support.

In the case of ASP.NET AJAX, you use the Sys.Application.add_navigate() method to hook up the navigate event. However, you’re extremely likely to want to combine all the grungy code into a class, from which you can then raise a standard .NET event which your managed code can then subscribe to. Here’s an example of how you might approach this:

[ScriptableType()]
public class HistoryNavigator
{
	public static HistoryNavigator
		Instance;
	static HistoryNavigator()
	{
		string script =
	@"function nav( sender, state ) {" +
			"document.getElementById('" +
			HtmlPage.Plugin.Id + "')" +
".Content.HistoryNavigator.DoNavigated(
			state.get_state().move);" +"}" +
"Sys.Application.add_navigate( nav );";
			HtmlPage.Window.Eval(script);
		Instance = new HistoryNavigator();
		HtmlPage.RegisterScriptableObject(
			"HistoryNavigator", Instance);
	}
	[ScriptableMember()]
	public void DoNavigated(string move)
	{
		Navigating(this, new
			NavigatedEventArgs(move));
	}

	public event
		EventHandler<NavigatedEventArgs>
			Navigated = delegate { };
// Other methods elided for clarity
	...
}

public class NavigatedEventArgs :
	EventArgs
{
	public readonly string Move;
public NavigatedEventArgs( string move )
	{
		Move = move;
	}
}
This is a fair chunk of code, so let’s take a few minutes to peek into its parts.

The first thing to understand is the class constructor. This performs three tasks, namely:

  1. It uses HtmlWindow.Eval() to execute a block of JavaScript that connects up the browser’s navigate event to a JavaScript method named nav, using the Sys.Application.add_navigate() method to do the work. The implementation of the nav method will be presented in more detail below.
  2. It implements a Singleton pattern to create a single HistoryNavigator object.
  3. It then registers that singleton instance as a type that is accessible from JavaScript.
Next up, note the HistoryNavigtor type exposes a scriptable method, DoNavigated, which takes in a string that represents the bookmark, and in turn raises a pure .NET managed event, Navigating. This event simply passes the bookmark string across inside a NavigatedEventArgs object.

The idea behind all this HistoryManager code is that you can use standard C# code to subscribe to the browser’s navigate event, such as:

HistoryManager.Instance.Navigated +=
	your handler method name here;
The only remaining piece of code to understand is the JavaScript code that actually handles the navigate event. It actually looks like this when it’s unpicked from the Silverlight code:
function nav( sender, state ) {
	document.getElementById( 'pluginId' ).
	Content.HistoryNavigator.DoNavigated(
		state.get_state().move );
}
Note that the pluginId will be the id that you select for your Silverlight control.

It is this nav() method that is called when the browser navigates. In turn, it calls across the HTML Bridge to the DoNavigating() method of the HistoryNavigator scriptable object that was registered in the class constructor, passing in the value of the move bookmark from the state object. You can see this flow in Figure 6.

Figure 6
Figure 6: Navigation event architecture

If you remember back to the earlier example, we used the keyword “move” when adding history points, which is passed through to the addHistoryPoint() method as shown below:

Sys.Application.addHistoryPoint(
	{ move : '2' },
	'X player took the top left cell' );
The nav() method retrieves the value by using the state parameter’s get_state() accessor, suffixed with the key name.

Using the HistoryNavigator

With all the goo code complete, you can now subscribe to the Navigated event in your Silverlight application and take the appropriate action. In the case of the Tic-Tac-Toe game, this is fairly trivial, as shown below:
void Page_Loaded(object sender,
	RoutedEventArgs e)
{
	HistoryNavigator.Instance.Navigated +=
		HandleNavigated;
	string moves =
		HtmlPage.Window.CurrentBookmark;
	if (!String.IsNullOrEmpty(moves))
	{
		if (moves.StartsWith("move="))
			moves = moves.Substring(5);
		ReplayMoves(
			HtmlPage.Window.CurrentBookmark);
	}
}

void HandleNavigated(object sender,
	NavigatedEventArgs e)
{
	ResetGame();
	ReplayMoves(e.Move);
}

void ResetGame()
{
// resets the game to the original
// starting point
...
}

void ReplayMoves(string moves)
{
// iterates through the move data
// making each move
...
}
As you can see in the above listing, each time navigation occurs the application resets the board and then replays each of the moves, by iterating across the bookmark data; remember that this will be of the form “#move=254”, for example, given that we have used the addHistoryPoint() method to populate the bookmarks.

Don’t forget that when your application loads for the first time, you also need to process any bookmark data. This is easily done in this simple game implementation just by reading the CurrentBookmark for the window when the page is loaded.

So, it looks as if both problems are now solved. You can use the ASP.NET AJAX Library’s Sys.Application.addHistoryPoint() to update the URL with a bookmark as the application state changes, allowing the user to bookmark that state and inject information into the browser history list. You can also use the library’s Sys.Application.add_navigate() method to connect a handler up to respond to the browser’s navigate event, thus allowing your application to react and read the bookmarked data and reset its state again.

Fantastic. Job done. Or is it?

The gotcha

You knew there had to be one (or possibly more than one), didn’t you? Nothing can be this easy.

Well, the fun part of combining these two parts together is that calling addHistoryPoint() will not only add an item into the history list and update the URL, but it will also cause the browser to navigate, resulting in your event handler being called.

It is thus critically important that your event handler does NOT call any code that calls addHistoryPoint(), otherwise your application will almost certainly hose the browser.

Similarly, you need to take care that when you call addHistoryPoint() that any navigation event handler doesn’t reset the state of your application in an inappropriate manner. The way that you do this can be as simple, or as complex, as you desire. In the case of this simple Tic-Tac-Toe application it was easy enough just to disable the event raising code with a simple flag, as shown below:

[ScriptableMember()]
public void DoNavigated(string move)
{
	if( !isSettingHistoryPoint )
		Navigated(this,
		new NavigatedEventArgs(move));
}

private static bool
	isSettingHistoryPoint = false;

public static void AddHistoryPoint(
	string title,
	string key,
	string value )
{
	try
	{
		isSettingHistoryPoint = true;
		string script =
			"Sys.Application.addHistoryPoint(
			{{ {0}:'{1}' }}, '{2}' );";
		HtmlPage.Window.Eval(string.Format(
			script, key, value, title));
	}
	finally
	{
		isSettingHistoryPoint = false;
	}
}

Conclusion

It is perfectly possible to implement a deep linking mechanism in a Silverlight 2 application, which also fully supports back/forward history navigation, using any of the off-the-shelf Ajax libraries that are freely available today. This article largely focused on using ASP.NET AJAX 3.5 SP1, given that the majority of Silverlight developers are likely to be familiar with, and deploying on, ASP.NET. However, you could use jquery or any other favourite implementation.

Of course, this functionality is entirely dependent upon the HTML Bridge being enabled because you’re relying on the use of JavaScript. Without it, you have no clean browser integration (at least, not at the time of writing).

Adding deep linking to your application can really make it feel that it is a true part of the Web. Bear in mind that implementing it can add to your workload, however, as you will have to consider how to manage that state. For a simple game such as Tic-Tac-Toe, or for a picture or media gallery, this is straightforward. More complex applications will require considerably greater work, although the technologies presented above will be the basis for that work.

Also bear in mind that you should only manipulate the history list in response to a user action; arbitrarily adding items to the list is likely to annoy a user. Also remember that integrating with the history list within a RIA might actually be disconcerting for a user, whether it is an Ajax application or a Silverlight application. Users tend not to like inconsistency, so ensure that you treat the history list as a user would expect.


Dave Wheeler is a writer, trainer and developer, who focuses on the various UI technologies available in Microsoft .NET. He delivers and authors training courses for DevelopMentor, including Essential Silverlight 2, and speaks at a number of industry-leading conferences, such as DevWeek and Software Architect. Dave is also a founding director of Rock Solid Knowledge, an exciting new consultancy company that helps clients to get the best out of the Microsoft .NET platform. You can reach him at dave@rocksolidknowledge.com.

Dave Wheeler is presenting a number of sessions at DevWeek 2009, including an all-day workshop on Silverlight on Monday 23 March, which can be booked independently from the main conference.

You might also like...

Comments

About the author

Dave Wheeler United Kingdom

Dave Wheeler is a freelance instructor and consultant who specialises in .NET application development. He’s a moderator on Microsoft’s ASP.NET and Silverlight forums and is a regular speaker at ...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“It is practically impossible to teach good programming style to students that have had prior exposure to BASIC. As potential programmers, they are mentally mutilated beyond hope of regeneration.” - E. W. Dijkstra