Silverlight 3 introduced to us a good navigation framework: you could combine Frames and UriMapper objects to change portion of your page (or the entire page) on the fly allowing you to navigate to different section of your application. There are plenty of resources available on the topic, just make a search for them.

Silverlight 4 further improved the model allowing us to easily customize portion of this framework too: we can for example change the way a new object (page) is loaded into a Frame implementing our own version of the INavigationContentLoader interface and assigning it to the ContentLoader property of the Frame object.

I won’t hide the fact I’m a big fun of writing ‘modular’ applications, so I tend to separate everything in components and use interfaces contracts for each module, an IoC container works very well in this scenario because you can think of it just like your service provider or an application entry point provider.

Being able to combine an IoC container with the Navigation Framework and the UriMapper will give us great flexibility, because we can easily swap part of the application just reconfiguring the objects inside the container, making the writing of a modular Silverlight application a - cough cough -breeze.

What I want to obtain is this:

<navigation:Frame x:Name="ContentFrame" 
                  Source="/Home" 
                  Navigated="ContentFrame_Navigated" 
                  NavigationFailed="ContentFrame_NavigationFailed">
    <navigation:Frame.ContentLoader>
        <helpers:IocNavigationContentLoader />
    </navigation:Frame.ContentLoader>
    <navigation:Frame.UriMapper>
        <uriMapper:UriMapper>
            <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
            <uriMapper:UriMapping Uri="/Search/{query}" MappedUri="Search?q={query}"/>
            <uriMapper:UriMapping Uri="/Album/{id}" MappedUri="Album?id={id}"/>
            <uriMapper:UriMapping Uri="/Search" MappedUri="Search"/>
            <uriMapper:UriMapping Uri="/Album" MappedUri="Album"/>
            <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
        </uriMapper:UriMapper>
    </navigation:Frame.UriMapper>
</navigation:Frame>

Here we have a mixed mapping:

  • Lines 10 and 15 set the routes to resolve our Uri to normal xaml pages (they have the default .xaml extension and I want to use the default PageResourceContentLoader here).
  • Lines 11-14 map the Uri to something that the default ContentLoader cannot resolve, so our custom ContentLoader will come in action.

A first rough implementation is easy to do:

public class IocNavigationContentLoader : INavigationContentLoader
{
	private static PageResourceContentLoader DefaultPageContentLoader = new PageResourceContentLoader();

	public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState)
	{
		if (targetUri.ToString().Contains(".xaml"))
			return DefaultPageContentLoader.BeginLoad(targetUri, currentUri, userCallback, asyncState);

		var iar = new IoCNavigationContentAsyncResult(asyncState);
		//Tries to load type from already loaded assemblies
		iar.Result = App.Container.Resolve(GetComponentName(targetUri));
		userCallback(iar);
		return iar;
	}

	private string GetComponentName(Uri uri)
	{
		return uri.ToString().Split('?')[0];
	}

	public bool CanLoad(Uri targetUri, Uri currentUri)
	{
		if (targetUri.ToString().Contains(".xaml"))
			return DefaultPageContentLoader.CanLoad(targetUri, currentUri);
		// check if the IoC can resolve the object
		return App.Container.Kernel.HasComponent(GetComponentName(targetUri));
	}

	public void CancelLoad(IAsyncResult asyncResult)
	{
		
	}

	public LoadResult EndLoad(IAsyncResult asyncResult)
	{
		if (asyncResult is IoCNavigationContentAsyncResult)
			return new LoadResult((asyncResult as IoCNavigationContentAsyncResult).Result);

		return DefaultPageContentLoader.EndLoad(asyncResult);
	}
}

public class IoCNavigationContentAsyncResult : IAsyncResult
{
	public IoCNavigationContentAsyncResult(object asyncState)
	{
		this.AsyncState = asyncState;
		this.AsyncWaitHandle = new ManualResetEvent(true);
	}

	public object Result { get; set; }

	public object AsyncState { get; private set; }

	public WaitHandle AsyncWaitHandle { get; private set; }

	public bool CompletedSynchronously
	{
		get { return true; }
	}

	public bool IsCompleted
	{
		get { return true; }
	}
}

The component logic is extremely simple: it will hold an instance of the PageResourceContentLoader (line 3) to be used for every Uri that contains the ‘.xaml’ string, all the rest will be asked to the IoC container.

The core functions are:

  • CanLoad (line 22): we check if it’s a plain xaml page, otherwise we ask the container (Castle Windsor in my case) if the component is registered.
  • BeginLoad (line 5): again...if it’s a plain xaml page, pass the control to the PageResourceContentLoader, otherwise get the service name from the mapped Uri and resolve it using the container.

Simple as that and it works! As a ‘side effect’ now your Pages can have injected dependencies to every service you like (logging, caching, searching, custom application services, etc...) and it all will be handled by the container.

This implementation was just a test, it’s not production code! a lot of improvement can be made, for example: you can take out the static container and use a service factory, add the ability to resolve object that reside on assemblies/xap downloaded on demand (it’s not that hard to do, believe me...again you can use the power of the UriMapper...there’s a very nice post by Corrado Cavalli on the topic, ok it’s in Italian...but the code explain itself).

Edit: I just noticed I forgot to post the container configuration for those two pages objects, so here it is:

Container.Register(
	Component.For<ISearchViewModel>().ImplementedBy<SearchViewModel>(),
	Component.For<ISearchView>().ImplementedBy<Search>().Named("Search"),
	Component.For<IAlbumViewModel>().ImplementedBy<AlbumViewModel>(),
	Component.For<IAlbumView>().ImplementedBy<Album>().Named("Album")
	);

As you can see each page is implemented following the MVVM pattern and it’s all resolved by the container.

Related Content