Silverlight 3.0 supports navigation by providing a Frame control into which you can load various pages as the user clicks on hyperlink controls in the application’s UI. Each page is specified by a Uri. As navigation takes place, the browser’s history and address bar is updated with this Uri. This allows the user to use the browser’s forward and back buttons to navigate around within the Silverlight application or to use deep links. You can map ‘friendly’ Uri’s to real page Uri’s so that the browser’s address bar doesn’t display the internal details of your application’s UI structure.
The Silverlight 3.0 navigation framework has some very interesting capabilities, but it does have some limitations too. One of these limitations is that all of the pages that can be navigated to have to be defined in the application’s primary XAP file. For composite applications, that typically consist of multiple XAP files, this has proven to be quite a problem. Various workarounds have been proposed but they are all pretty clunky. The underlying problem is that the Silverlight 3.0 navigation framework doesn’t provide any extensibility hooks to implement this capability properly.
Happily, this is about to change in Silverlight 4.0, which will allow you to specify a Content Loader for each Frame instance:
1: myFrame.ContentLoader = new CustomContentLoader();
The content loader is responsible for loading whatever content is associated with the target Uri. By giving folks the ability to create custom content loaders of their own, Silverlight 4.0’s navigation framework opens up some very interesting extensibility options. A custom content loader, for example, can remove the need for clunky shims and pass-through loader pages when navigating to pages located in other XAP files. They can also allow you to use navigation Uri’s which don’t necessarily have to map directly to UI content, or allow you to control the target page’s construction (so you can hook up it’s ViewModel), etc.
There is a rather large drawback with custom content loaders though: they are executed asynchronously. This makes them quite tricky to implement. A custom content loader implements the INavigationContentLoader interface which includes Begin and End methods and uses an IAsyncResult object to coordinate the asynchronous interaction between the Frame and the content loading logic. This asynchronous call pattern is known as the .NET Asynchronous Programming Model (APM). It’s notoriously tricky to implement properly – not least of which because you have to account for the 3 or 4 ways in which the caller can execute the call, and you have to handle exceptions, cancellations and wait handles very carefully.
So why is navigation executed asynchronously you may ask. That’s a very good question! I was involved in the early design discussions for the Silverlight navigation framework and argued against an asynchronous model for a couple of reasons. My main argument was that navigation invariably means you have to construct the new UI to which you’re navigating, and this has to happen synchronously on the UI thread. My other argument was to do with user experience – more on this later.
Ah, yes, (so the counter-argument went) but what happens if you want to download some content/data from the host web site, or make a web service call, or load data from a database, or maybe do some complex calculation – you can’t tie up the UI thread! These are all valid things to do in an application of course, just not within the navigation operation itself!!! Apart from anything else, these are pretty advanced scenarios and by far the majority of navigation scenarios will just involve mapping Uri’s to UI content which just needs to be loaded (on the UI thread!!). In addition, all of these scenarios can be achieved fairly easily on top of a synchronous navigation model, and in a way that makes for a better user experience too.
Ok, rant over. I feel much better now :-) INavigationContentLoader is what it is. The Silverlight folks have worked hard to open up the navigation framework a little and given us a key extensibility point. We just have to try and leverage it as best we can…
So, in this post I’ll describe the implementation of a base class (ContentLoaderBase) that can be used to easily implement custom content loaders in Silverlight 4.0. You can use this base class to implement any number of different content loaders, including synchronous content loaders or asynchronous content loaders that download content in the background or that use a background thread for background processing. Hopefully this base class will provide you with a reasonable starting point for implementing whatever type of content loader you need in your application. At the end of this post I’ll talk briefly about how the ContentLoaderBase base class works.
You can download the sample code here.
It’s built on Visual Studio 2010 Beta 2 and Silverlight 4.0 Beta. The sample shows how to configure and use various synchronous and asynchronous content loaders and their effect on navigation. You can switch between the sample content loaders in MainPage.xaml.cs.
The ContentLoaderBase base class provides a small number of virtual methods. You can implement a synchronous or an asynchronous custom content loader simply by overriding the right methods. In the simple (and most common) case of an synchronous content loader, you simply need to override a single method – LoadContentSync – which looks like this:
1: protected override object LoadContentSync( Uri targetUri,
2: Uri currentUri )
4: return new ControlOrPage();
In this method you simply create or load whatever UI content you need based on the new (and maybe the current) Uri. You can do this in any number of ways – using a switch statement or using a lookup table for example. The important thing to note is that this method is called on the UI thread so you don’t have to worry about creating UI content or making cross-thread calls.
Of course, since the call is made on the UI thread, you shouldn’t do crazy things like make a network call or do a lengthy calculation in this method. If you need to do anything like that then you may need an asynchronous content loader (though I’ll argue later that even if you do need to do anything like this you shouldn’t do it during navigation). In the meantime here’s how you can do it using the ContentLoaderBase class…
You can implement an asynchronous content using the ContentLoaderBase class by overriding two virtual methods (and implementing one or two callbacks). The first method to override is the GetLoadBehavior method which defines whether the navigation operation for a particular Uri is synchronous or asynchronous (the default value is synchronous which is why we didn’t need to override this method in the synchronous case above).
1: protected override LoadBehavior GetLoadBehavior( Uri targetUri )
3: // Content is loaded asynchronously for all Uris.
4: return LoadBehavior.Asynchronous;
In this example, navigation for all Uri’s is asynchronous, though we could of course have some Uri’s handled synchronously and some handled asynchronously (which is useful when, for example, we’ve cached content that was previously downloaded asynchronously so that subsequent navigations can be handled synchronously).
The other method you need to override is the LoadContentAsync method. This is where you initiate whatever asynchronous operation you need. When that operation is completed, you simply need to call the LoadContentAsyncComplete method in the base class to complete the navigation operation.
1: protected override void LoadContentAsync(
2: ContentLoaderAsyncResult asyncResult )
4: // Initiate asynchronous operation here...
6: // When the asynchronous operation is completed, call the
7: // LoadContentAsyncComplete method on the base class to
8: // complete the navigation operation.
Broadly speaking, there are two types of asynchronous operations that you might want to do when loading content during a navigation operation. The first involves downloading some form of content, data or configuration from the host web site. The second involves performing some form of processing or calculation on a background thread.
Either way, once the async operation is completed you will still need to load or create the actual UI content to which you’re navigating on the UI thread. The way you do that in each of these two scenarios are slightly different and may require you to implement one or more callbacks. Let’s look at each of these scenarios in turn…
Retrieving or sending data from the host web site is a common scenario in Silverlight. The Silverlight WebClient class makes this fairly straightforward by providing an asynchronous event-based callback mechanism – you can start the download or upload process and the WebClient class will call you back when its complete. They key feature of this class is that the callback happens on the UI thread, which in turn means that you don’t have to worry about cross-thread UI access. It’s a pity that the navigation framework didn’t adopt the same event-driven approach… Ho hum…
To use the WebClient class in a content loader, we simply need to start a download process (or whatever we need) in the LoadContentAsync method and specify a callback method. Since the callback method is called on the UI thread we can load or create whatever UI content we need there. The completed sample looks something like this:
2: ContentLoaderAsyncResult asyncResult )
4: // Use the WebClient class to download a file from the
5: // web server. This is an asynchronous method call and
6: // will return immediately.
8: new Uri( "NavigationData.xml", UriKind.Relative ),
9: asyncResult );
12: private void OnOpenReadCompleted( Object sender,
13: OpenReadCompletedEventArgs e )
15: ContentLoaderAsyncResult asyncResult =
18: // Since the WebClient callback is on the UI thread,
19: // we can create the UI we're navigating to here.
20: Object content = new ControlOrPage();
22: // Inform the base class that the async request has
23: // been completed.
24: base.LoadContentAsyncComplete( content, asyncResult );
Notice how the UI content that was created in the callback method is passed back to the base class via the LoadContentAsyncComplete method call. The base class then completes the asynchronous navigation operation and passes the content to the Frame.
The other scenario where you might want to use an asynchronous content loader is where you want to use a background thread to perform some kind of calculation or background process. In this case, the LoadContentAsync method might look something like this:
4: // Use the thread pool to queue up a background operation.
5: ThreadPool.QueueUserWorkItem( BackgroundTask, asyncResult );
Where BackgroundTask is the method that defines the background process. This method will of course be called on the background thread, not the UI thread, so when the calculation is finished how do we load the UI content that we need to navigate to? To do that, when we inform the base class that the calculation has finished, we provide another callback. The base class makes sure that this callback will be called on the UI thread so we can load our UI content there. The completed sample looks something like this:
1: private void BackgroundTask( Object state )
3: ContentLoaderAsyncResult asyncResult =
6: // Simulate some lengthy calculation or process.
7: Thread.Sleep( 4000 );
9: // We can't create the necessary UI content
10: // here because we're on a background thread.
11: // So we create the UI in a callback method.
12: // The base class will call the callback on the UI thread.
13: base.LoadContentAsyncComplete( LoadContentUICallback,
14: asyncResult, asyncResult );
17: private Object LoadContentUICallback( Object callbackState )
19: Debug.WriteLine( "LoadContentUICallback" );
21: ContentLoaderAsyncResult asyncResult =
24: // Since THIS callback is on the UI thread, we can
25: // create the UI we're navigating to here.
26: return new ControlOrPage();
The above code snippets are simplified a little to show you the essentials. If you have an asynchronous content loader you will find that a navigation operation will often be canceled. To implement an asynchronous content loader properly you will therefore have to account for cancelation.
Why is navigation canceled so often? Because the navigation framework does not provide any built-in mechanism to give feedback to the user during an asynchronous navigation operation. Since the user expects navigation to be instantaneous, they have a tendency to click the navigate button or hyperlink control multiple times, and each time they do, the previous navigation operation will be cancelled. So it’s very important to handle cancellations properly, otherwise you can easily run into tricky-to-handle exceptions during navigation. The irony of course, is that asynchronous navigation was meant to improve the user experience…
To illustrate the effects of network latency or lengthy processing during navigation, the sample code includes artificial delays. For the download sample, a HTTP handler is used to introduce an artificial delay during the download of the dummy file. Similarly, the background thread sample includes a 3 second Thread.Sleep in the background task method.
When navigation is asynchronous, you’ll quickly notice that clicking on the navigation links during an existing navigation operation will cause the previous navigation operation to be canceled. The sample code shows how you might handle this situation but there are some complications that have not yet been ironed out (see the comments in the code for more details). In the download case you will need to somehow cancel the previous download operation. In the background thread case you will have to find a way to abort or cancel the calculation or background process gracefully if navigation is cancelled…
The key to the ContentLoaderBase Class is the object that implements the IAsyncResult interface. For that I leveraged the excellent coding skills of Jeffery Richter and his MSDN magazine article (here) from a few years back. That article and the accompanying code, describes many of the subtle details that you have to worry about when implementing the APM. With the IAsycnResult object implemented properly, the ContentLoaderBase class ‘simply’ has to make sure that the right virtual methods are called in the right order and that the LoadContentUICallback is executed on the UI thread.
The ContentLoaderBase class simplifies things quite a bit, especially when implementing a synchronous content loader. But I think you can probably see that asynchronous content loaders are still a little complicated to implement properly. I’m sure that I have not even begun to iron out all of the subtle issues that inevitably show up when you’re doing anything asynchronously (e.g. cancelation as noted above).
For reasons of complexity, plus the bad user experience that can result, I would recommend using only synchronous content loaders wherever possible. But what about those situations where you absolutely need to download or calculate something during navigation? My advice is to do these things outside of the navigation framework and to do them in the target page itself.
So for example, for the two scenarios above, you would navigate to a page (synchronously) that will then download the content or perform the calculation (in its ViewModel of course). Furthermore, while the page is downloading the content or performing the calculation, it can provide visual feedback to the user! To the user, the navigation is instantaneous, even though the target page might take a while to initialize itself. By providing visual feedback while this happens you will likely prevent the user from getting frustrated and repeatedly clicking the navigate button or link.
I’ll explore this approach, plus some other interesting things you can do with a custom content loader, in a future post…