I recently had a query from a customer that was one of those ones that you think “Aha! That’s easy, you just do this”. Then you think a bit more about it and realise that if you want to do it properly it’s not quite as simple as you first thought.

This particular query related to how to use the navigation framework introduced in Silverlight 3 with the MVVM pattern. For those that don’t know the Model-View-ViewModel pattern is a common pattern often used to build Silverlight and WPF applications since it has strong support for databound UIs and provides good abstraction for unit testing and keeping your view logic separate from the view and the model.

Enter the Silverlight Navigation Framework

Handling the page style navigation that we’ve all become used to on the web can be a real pain in RIAs written using Silverlight, Flash or AJAX involving lots of tracking of page clicks using HTML bookmarks and some liberal use of JavaScript. Fortunately Silverlight 3 added a navigation framework to help with this.

This framework works by you adding a Frame element to your root XAML and then creating many Page derived classes. These classes can then navigate between each other by calling into a NavigationService instance that each page inherits from it’s base class.

The issue with MVVM is that page navigation is a view logic, that means that it should sit inside the ViewModel, however the NavigationService is only on the Page class which the ViewModel doesn’t have access to.

Solving this problem is simple, just pass the NavigationService instance to the ViewModel, job done! Well ok, that works if you hate unit testing. If you like unit testing though you may find you have problems then when it comes to testing your ViewModel as you won’t have a NavigationService instance to pass it.

Additionally since many pages are likely to want to do this I’d like to have a reusable approach that doesn’t take much code to reuse.

The Solution

The solution I came up with is one of a few different ways you could implement this but this works for me at the moment. Feel free to point out any glaring errors in my design though.

Wrapping the NavigationService

First I need to make the NavigationService mockable so I can use it in unit tests. To do this I created a new interface INavigationService that exposes the methods and properties of the NavigationService. This sample version only exposes Navigate() but you could easily expose more as needed.

1 public interface INavigationService
2 {
3     void Navigate(string url);
4 }

Not the most complex interface ever devised you’ll agree.

Next I created a class that implemented the interface and took a reference to a System.Windows.Navigation.NavigationService on it’s constructor.

01 public class NavigationService : INavigationService
02 {
03     private readonly System.Windows.Navigation.NavigationService _navigationService;
04  
05     public NavigationService(System.Windows.Navigation.NavigationService navigationService)
06     {
07         _navigationService = navigationService;
08     }
09  
10     public void Navigate(string url)
11     {
12         _navigationService.Navigate(new Uri(url, UriKind.RelativeOrAbsolute));
13     }
14 }

Supporting INavigationService in the ViewModel

Now I had an abstraction I needed to pass the INavigationService to the ViewModel. I wanted to do this in a standard way. I could have made it a constructor argument but I couldn’t always guarantee I’d be there at construction. The best way seemed to be to add a property. I decided to put that property on an interface so I had a defined contract that ViewModels could support.

INavigable.

1 public interface INavigable
2 {
3     INavigationService NavigationService { get; set; }
4 }

This interface provides a single property that a ViewModel can implement that will contain a reference to the INavigationService that the ViewModel should use to perform navigation when it needs to.

Passing the INavigationService to the ViewModel

Next I need to create an instance of the NavigationService that wraps the System.Windows.Navigation.NavigationService in the Page class and pass that to the ViewModel. I’d like this to be reusable code and if possible I don’t want any code behind in my View.

This is a perfect use for an attached behaviour. What’s one of those? It’s simply an attached property with a property changed handler on it that hooks up code to the DependencyObject that the property gets attached to. It’s simpler than it sounds and is a nice way of making reusable logic that you want to attach to objects in XAML.

The Navigator.

01 public static class Navigator
02 {
03     public static INavigable GetSource(DependencyObject obj)
04     {
05         return (INavigable)obj.GetValue(SourceProperty);
06     }
07  
08     public static void SetSource(DependencyObject obj, INavigable value)
09     {
10         obj.SetValue(SourceProperty, value);
11     }
12  
13    public static readonly DependencyProperty SourceProperty =
14         DependencyProperty.RegisterAttached("Source", typeof(INavigable), typeof(Navigator), new PropertyMetadata(OnSourceChanged));
15  
16     private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
17     {
18         Page page = (Page) d;
19  
20         page.Loaded += PageLoaded;
21     }
22  
23     private static void PageLoaded(object sender, RoutedEventArgs e)
24     {
25         Page page = (Page)sender;
26  
27         INavigable navSource = GetSource(page);
28  
29         if (navSource != null)
30         {
31             navSource.NavigationService = new NavigationService(page.NavigationService);
32         }
33     }
34 }

This class provides a single attached property definition of the type INavigable. This property has a handler that when invoked grabs the DependencyObject you are attaching the property to and hooks up it’s Loaded event.

So when you attach this property to a Page instance in XAML it will fire off the PageLoaded method when the Page you attach it to loads. In the page load handler I then query the Page instance for the Source attached property. Remember that this property is of type INavigable. If the source supports INavigable I then create a new instance of the NavigationService, wrapping the Page’s instance and set it on the source using the INavigable.NavigationService property.

And there we have it, a reusable way of attaching the navigation service instance for a Page to the Page’s view model.

The View XAML

And finally using the attached property is a case of assigning the ViewModel to the DataContext of the Page as normal and then doing this.

01 <navigation:Page x:Class="SLNavigation.Page1"
02            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
03            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
04            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
05            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
06            mc:Ignorable="d"
07            xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
08            xmlns:SLNavigation="clr-namespace:SLNavigation"
09            d:DesignWidth="640" d:DesignHeight="480"
10            Title="Page1 Page"
11            SLNavigation:Navigator.Source="{Binding}"
12     >

Summary

So that’s it, my solution. I think it’s reasonable, it minimised the amount of code needed in the View code behind to nil, makes the only code you need on the ViewModel is to derive from and implement INavigable which is one property and to put an attached property on the Page in the XAML. It keeps a good separate of concerns as the ViewModel is still unit testable but it can now support navigation.

Let me know what you think.

 

Originally posted by Robert Garfoot on 8th September 2010 here: http://garfoot.com/blog/2010/09/silverlight-navigation-with-the-mvvm-pattern/