Last week, I gave an introduction to INavigationContentLoader, a Silverlight 4 Beta SDK extensibility point that allows you to load arbitrary content in conjunction with the Silverlight navigation feature, and walked you through building a Typename-based content loader.  That’s all well and good, but it’s an awful lot of work to go to in order to build a simple INavigationContentLoader (following the async pattern, fully implementing the interface, etc.).  Sometimes, all you really want to do is handle a few events in order to be up and running, fully integrated with Silverlight navigation.  Furthermore, because of the nature of the feature, Navigation has a high potential for error conditions.  For example, what happens if a user tries to follow a broken link or types a bad address into the browser’s address bar (with a browser-integrated frame)?  How do you gracefully let your users know that something went wrong and give them some options about what to do next?

In this post, I’ll use the INavigationContentLoader extensibility point in the Silverlight 4 Beta SDK to address these two issues.  For convenience, starting with this post, I’ll be compiling my helper classes and utilities into a set of libraries I’ll call “Silverlight and Beyond” (SLaB), which you’re more than welcome to download and use (although I make no guarantees about its functionality or that I won’t introduce breaking changes as I make additions).

An Event-Based INavigationContentLoader

For simple custom content loading like we did with the Typename-based content loader, writing a full implementation of an INavigationContentLoader can really be overkill.  For quick-and-dirty navigation scenarios, it would be much easier to just use a generic INavigationContentLoader and handle some events in order to perform a load operation.  In the simplest case, loading is just a synchronous operation that creates an instance of a page based on your logic (a simple switch statement might be appropriate, and this gives you an opportunity to provide constructor parameters or do other initialization on your pages).

With that in mind, I threw together a SynchronousEventContentLoader that simplifies these cases.  Now, you we can rewrite the Typename-based content loader as follows:

In MainPage.xaml:

<navigation:Frame x:Name="ContentFrame"
              Source="/TypenameEventContentLoader.Views.Home">
    <navigation:Frame.ContentLoader>
        <loader:SynchronousEventContentLoader Load="SynchronousEventContentLoader_Load"
                                              CanLoad="SynchronousEventContentLoader_CanLoad" />
    </navigation:Frame.ContentLoader>
</navigation:Frame>

In MainPage.xaml.cs, we handle the Load/CanLoad events as follows (virtually identical to our previous impelementation):

private LoadResult SynchronousEventContentLoader_Load(Uri targetUri, Uri currentUri)
{
    Type t = Type.GetType(GetTypeNameFromUri(targetUri), false, true);
    object instance = Activator.CreateInstance(t);
    var result = new LoadResult(instance);
    return result;
}

private bool SynchronousEventContentLoader_CanLoad(Uri targetUri, Uri currentUri)
{
    string typeName = GetTypeNameFromUri(targetUri);
    Type t = Type.GetType(typeName, false, true);
    if (t == null)
        return false;
    var defaultConstructor = t.GetConstructor(new Type[0]);
    if (defaultConstructor == null)
        return false;
    return true;
}

private string GetTypeNameFromUri(Uri uri)
{
    if (!uri.IsAbsoluteUri)
        uri = new Uri(new Uri("dummy:///", UriKind.Absolute), uri.OriginalString);
    return Uri.UnescapeDataString(uri.AbsolutePath.Substring(1));
}

And that’s it!  If you just want a quick switch statement so that you can add initialization (constructor parameters, etc.) to your pages, this is a nice, easy way to go.  If there’s a need for something similar that supports asynchronous operations, making a similar INavigationContentLoader for that is fairly straightforward.

Handling Errors using an INavigationContentLoader

There’s a large variety of errors that can occur during navigation – the Uri may not resolve to anything real, the Page may throw an exception while being constructed, your user doesn’t have permission to see a particular page, or your particular INavigationContentLoader may hit an exceptional condition (no network access, invalid syntax when parsing a config file, etc.).  In order to develop a robust application, we need to be able to handle these error conditions gracefully.  For those of you familiar with web development, this is not too dissimilar from writing error pages to handle 404 (page not found) conditions, etc.

One way to handle error conditions with the Navigation framework is to handle the NavigationFailed event on the Frame or NavigationService, but this means that when navigation does fail, your only options are to navigate to a new page altogether (resulting in extra history entries and the potential for further failures) or to show some other UI, which may leave your users with a blank page in front of them (in the Frame) if they navigate to the page directly (rather than from a link in your application).  Our approach will be to load a different page altogether when loading content fails.  For this, I’ve created an ErrorPageLoader, another INavigationContentLoader.

Of additional importance is that this content loader doesn’t really own the content loading process from end to end.  In order for the ErrorPageLoader to be generalized, it must be composable, allowing it to delegate loading to another INavigationContentLoader except during error conditions.  In this case, the ErrorPageLoader has two INavigationContentLoaders that it may delegate its work to:

  • ErrorPageLoader.ContentLoader, which is what the ErrorPageLoader will attempt to use in order to service standard load requests.  This defaults to the PageResourceContentLoader built into Silverlight 4, which is what the Frame/NavigationService use by default.
  • ErrorPageLoader.ErrorContentLoader, which is what the ErrorPageLoader will use to load pages in case the ErrorPageLoader.ContentLoader fails.  If this value is null, the ErrorPageLoader uses ErrorPageLoader.ContentLoader to load the error pages.

This dichotomy allows you to delegate to a more reliable INavigationContentLoader (e.g. the PageResourceContentLoader) should your primary one fail.  For example, if I use an INavigationContentLoader that downloads external assemblies on the fly, and the downlaod fails, error pages should be loaded locally rather than trying to load from a remote location.

Next, we need a way to specify what Uri to load as an error page based on the type of error that occurred.  The ErrorPageLoader has an ErrorPages property that takes a set of IErrorPages – an interface that maps an exception type to a Uri.  I’ve provided a default implementation for use in XAML that takes a name of an Exception type to handle (or none at all, in which case it becomes a catch-all handler) and a Uri (unmapped) to load using the ErrorContentLoader when those error conditions arise.

With these things in mind, the usage of the ErrorPageLoader looks like this:

<navigation:Frame x:Name="ContentFrame" 
                  Source="/Home">
    <navigation:Frame.UriMapper>
      <uriMapper:UriMapper>
        <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
        <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
      </uriMapper:UriMapper>
    </navigation:Frame.UriMapper>
    <navigation:Frame.ContentLoader>
        <loaders:ErrorPageLoader>
            <loaders:ErrorPage ExceptionType="System.InvalidOperationException"
                               ErrorPageUri="/Views/InvalidOperationErrorPage.xaml" />
            <loaders:ErrorPage ExceptionType="System.InvalidCastException"
                               ErrorPageUri="/Views/InvalidCastErrorPage.xaml" />
            <loaders:ErrorPage ErrorPageUri="/Views/DefaultErrorPage.xaml" />
        </loaders:ErrorPageLoader>
    </navigation:Frame.ContentLoader>
</navigation:Frame>

There are just a few things to note here:

  • Because I didn’t specify an ErrorPageLoader.ContentLoader in the example above, the PageResourceContentLoader will be used to load pages normally.
  • The ExceptionType isn’t actually of type Type, since Silverlight XAML doesn’t support custom properties of type Type at the moment.
  • The ErrorPageUri is an unmapped Uri (i.e. it does not follow the UriMapper-based scheme)
  • When an error page is loaded, the CurrentSource of the Frame/NavigationService (or the browser if the Frame is browser-integrated) remains the same.  If you want the Uri to change, use the ErrorRedirector INavigationContentLoader that I’ve provided to redirect to the error Uri.

The final step in the process of error handling is to provide some good feedback to your users about what went wrong.  In order to do this, it’s useful to know what exception occurred that caused the error page to be loaded.  As such, the ErrorPageLoader has an attached “Error” property, which it attaches to any page loaded using the ErrorContentLoader.  This attached property holds the exception that was thrown during loading.

I threw together a little example of an error page that uses this attached property, like so (alternatively, I could have bound to it in XAML):

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    this.errorInformation.Content = ErrorPageLoader.GetError(this);
    this.uriLink.NavigateUri = e.Uri;
}

You can see an example of the ErrorPageLoader in action here:

Live ErrorPageLoader sample

And here are some things to try:

Notice what happens to the Uri (everything after the “#” in the URL) when navigating to pages that are in error.  Try changing the URI to anything you can think of – you’ll always be able to navigate there, but the links will just lead you to error pages.

If you’re really brave, here are some other experiments to consider:

  • Wrap the ErrorPageLoader around the Typename-based content loader from my previous post, and try navigating to Types without a default constructor or that don’t exist
  • Wrap the ErrorPageLoader around your own INavigationContentLoader (or the SynchronousEventContentLoader).  When do you expect loading to fail?  What should the user experience be when something does go wrong?  (here’s an idea: throw an exception if the user isn’t logged in, and use the ErrorPageLoader to show a login page)

So… How does it work?

Well, the behavior of the ErrorPageLoader is pretty straightforward:

  • Try to load the page from the targetUri using the ErrorPageLoader.ContentLoader
  • If an exception is thrown during that INavigationContentLoader’s BeginLoad or EndLoad, or if the loader returns something invalid (e.g. the LoadResult contains something that’s not a Page or a UserControl)…
    • Find the first IErrorPage in ErrorPageLoader.ErrorPages
    • If an IErrorPage was found, try to load the Uri that the IErrorPage maps to using ErrorPageLoader.ErrorContentLoader
      • If the LoadResult has a Page rather than a redirect, attach the Exception using the Error attached property
    • Otherwise, throw the exception during EndLoad() (i.e. allow the exception to fall through)
  • Return the LoadResult

And really, that’s the nature of a composable INavigationContentLoader: add some functionality around an existing INavigationContentLoader, and pass the remaining results on through to the Frame/NavigationService.  Such content loaders make it easy to decorate existing loaders with functionality that applies to your application.  I’ve got a few ideas for these, but I’d love to hear what you think!  Here are a few that I have on my mind:

  • Error pages (in case you didn’t just finish reading about those :) )
  • Authenticating/Authorizing (redirect me to a login page or an error page if I’m not authorized to see the one I requested)
  • QueryString initialization (reflect over properties on the page to set based upon the query string of the Uri)
  • Chainer (if the first INavigationContentLoader in my list didn’t work, try the next one!)

With that said, what else would you like to see?  If something particularly compelling arises, I’d be happy to add it to my “Silverlight and Beyond (SLaB)” library!

And, as always, the goods…

You didn’t really think I’d leave you without some code and samples, did you?

  • Live sample
  • SLaB v0.0.1 (includes source, a sample app, some tests, and binaries)
    • For the latest version, please check out SLaB on my Downloads and Samples page.
    • The v0.0.1 download of SLaB includes:
      • ErrorPageLoader and related classes
      • SynchronousEventContentLoader

P.S.  What on Earth is SLaB?

I mentioned it briefly at the beginning of this post, but my plan is to start putting all of my utility classes, tools, controls, and so forth in one easily-downloadable and easy-to-use package.  For now, I’m calling this SLaB, or “Silverlight and Beyond” (just because I’m into really corny acronyms).  Hopefully you’ll find it useful either as an example of what to do (or if you really dislike me – what not to do :) ), or to use within your own applications.  If you’re curious about how or why I did something, or if you find a bug, let me know!  Who knows?  Maybe you’ll inspire another blog post!

Just one note: it’s just little ol’ me working on SLaB, and I make no guarantees of correct behavior, frequent updates, lack of breaking changes, full documentation, support, or anything of the sort.  Nonetheless, I look forward to adding to it whenever my curiosity is piqued enough to make something useful or demonstrative, and I hope you find it useful too!