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
publicinterfaceINavigationService
2
{
3
voidNavigate(stringurl);
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.
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.
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.
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.
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.