One of the extensibility points added to the run-time in Silverlight 4 is the new INavigationContentLoader interface, which allows applications that use Silverlight’s navigation framework to take control of page loading. In Silverlight 3, navigation URIs had to target XAML files containing Silverlight pages. In Silverlight 4, a navigation URI can target anything—a class name, a Web service that provides page content dynamically, or a XAML file contained in a remote XAP, for example—as long as an INavigationContentLoader implementation is present to support it.

David Poll has blogged extensively about INavigationContentLoader and about the different scenarios in which it can be used. Here, for example, is a great post on using a custom content loader to load pages from XAPs that are downloaded on demand. Here’s another that demonstrates how to use a custom content loader to authorize access to a page before navigating to it. It seems there is no end to the purposes that INavigationContentLoader might serve.

To demonstrate the bare-bones basics of INavigationContentLoader, I implemented one that serves a simple purpose: it appends “.xaml” to the file names of the pages targeted by navigation URIs. Rather than declare your Frame control and URI mappings this way:

<nav:Frame x:Name=”Main” Source=”Home”>

    <nav:Frame.UriMapper>

        <map:UriMapper>

            <map:UriMapping Uri=”” MappedUri=”/Index.xaml” />

            <map:UriMapping Uri=”Home” MappedUri=”/Index.xaml” />

            <map:UriMapping Uri=”Detail/{id}” MappedUri=”/Detail.xaml?id={id}” />

        </map:UriMapper>

    </nav:Frame.UriMapper>

</nav:Frame>

With my CustomContentLoader class, you do it this way instead:

<nav:Frame x:Name=”Main” Source=”Home”>

    <nav:Frame.UriMapper>

        <map:UriMapper>

            <map:UriMapping Uri=”” MappedUri=”/Index” />

            <map:UriMapping Uri=”Home” MappedUri=”/Index” />

            <map:UriMapping Uri=”Detail/{id}” MappedUri=”/Detail?id={id}” />

        </map:UriMapper>

    </nav:Frame.UriMapper>

    <nav:Frame.ContentLoader>

        <local:CustomContentLoader />

    </nav:Frame.ContentLoader>

</nav:Frame>

Note the new ContentLoader property on the Frame control, which allows you to replace Silverlight’s default content loader with a custom content loader. The default content loader is an instance of PageResourceContentLoader, which loads pages from local XAML files. My CustomContentLoader wraps an instance of PageResourceContentLoader and appends “.xaml” to target URIs before delegating to PageResourceContentLoader to do the actual loading. Here’s the source code:

public class CustomContentLoader : INavigationContentLoader

{

    private PageResourceContentLoader _loader = new PageResourceContentLoader();

       

    public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri,

        AsyncCallback userCallback, object asyncState)

    {

        return _loader.BeginLoad(AddFileNameExtension(targetUri),

            currentUri, userCallback, asyncState);

    }

 

    public bool CanLoad(Uri targetUri, Uri currentUri)

    {

        return _loader.CanLoad(AddFileNameExtension(targetUri), currentUri);

    }

 

    public void CancelLoad(IAsyncResult asyncResult)

    {

        _loader.CancelLoad(asyncResult);

    }

 

    public LoadResult EndLoad(IAsyncResult asyncResult)

    {

        return _loader.EndLoad(asyncResult);

    }

 

    private Uri AddFileNameExtension(Uri uri)

    {

        int index = uri.OriginalString.IndexOf(‘?’);

 

        if (index == –1) // No query string; append extension

            return new Uri(uri.OriginalString + “.xaml”, UriKind.Relative);

        else // Insert extension before query string

            return new Uri(uri.OriginalString.Insert(index, “.xaml”), UriKind.Relative);

    }

}

Note the asynchronous design of INavigationContentLoader. When you implement it, you often have to implement IAsyncResult, too. I didn’t have to in CustomContentLoader because I used the IAsyncResult returned by PageResourceContentLoader. While the asynchronous design complicates matters when writing a synchronous loader, it’s perfect for content loaders that load pages using Silverlight’s asynchronous networking stack.

This is a trivial example, but it still demonstrates the power of INavigationContentLoader. The upshot is that in Silverlight 4, navigation URIs no longer have to target local XAML files, but can be virtualized instead. Look for this to come in handy not only for application developers, but for future versions of Silverlight as well.