Adapting Silverlight Navigation to MVVM
The Navigation feature in Silverlight is pretty awesome. It adds support for two very critical issues:
1. Real web-compliancy (Browser Address changes, which adds support for: SEO, Deep Linking, Browser Journal)
2. View-Switching navigation (switching views easily to solve the ever-lasting navigation problem)
It’s important to notice that these two issues are actually completely different problems, which for some reason were bundled together!
Many times you would want to switch views in different scenarios, but you might not want to change the Url, and even more often than that, you’d want to change the Url, but remain in the same view (e.g. going from a page the shows Product #5 to Product #7 – you’d want to use the same view, but to change the url to enjoy browser integration)
1. Those two issues should not be bundled together.
2. The view navigation support is naïve at best. Most real world scenarios are more complex and the navigation framework was not built to allow such scenarios.
In real world I would not suggest using this for view-navigation. Either go light-weight with the MVVMLight way, or heavy weight with Prism navigation (which is IMHO the best and handiest feature of the entire Prism toolset).
3. The browser features are amazing and extremely important, but I would like that to be stripped away from the view-navigation completely, since it’s not really related to actual view-navigation.
and most importantly –
4. I should be able to control the browser features from the View-Model, allowing for clean MVVM solution.
Specifically here, the navigation controls (Frame & UriMapper) are not MVVM-ready, and it’s rather annoying. I would expect the P&P team to work closer to the .Net teams so that the gap between them both would be smaller. But that’s really a story for another post.
Silverlight Navigation – The way it is now
Using the Navigation Application Template in Visual Studio offers a really fast Quick Start, and it’s pretty easy to understand how the different parts are working.
The main page now have the following Frame control:
The frame control is the only place we can get Navigation-related events, and the Source property is not data-boundable.
The Uri-Mapper, allowing the mapping of custom uris to actual files is very cool, but isn’t very helpful in our MVVM-quest.
In order to navigate from one view to another, we use the hyper-link button:
Out of the box, the way to control Navigation controls comes in two flavors.
1. Manipulating the Frame directly via code behind.
2. Using HyperLinks to Navigate to different Uris.
The way it should be, MVVM Style
I would really like to be able to use the Navigation feature in an MVVM-like way, namely:
1. I would like to have a NavigationHelper that allows me to change the Uri of my application without actually loading another view, or loading my current view again from scratch.
2. I would like the NavigationHelper to be available from the ViewModel, so I could control the deeplinking feature from the ViewModel layer, the way it should be done, and not automatically from the view itself.
I’m not interesting in the view-switching capabilities at-all. In real world scenarios, I would need more control over the navigation process than these controls allows me to begin with.
All is not lost though
Although out-of-the-box it’s not working the way you’d want it to be, it is, however, quite easy to customize to our needs.
Creating MVVM-Ready NavigationHelper
Our first task is to create a NavigationHelper class that would be easy to handle from the view-model layer.
The Navigation Helper should expose the following:
1. Navigate(uri): changes the uri
2. GoBack & GoForward methods
3. GetQueryStringParameter method which would allow me to easily extract parameters from the uri
4. Events exposing the navigation (Navigated, Navigation, NavigationFailed & NavigationStopped) so that other ViewModels can “listen” to Navigational events.
(full code below)
This would allow us to change the uri from the ViewModel (without actually switching views if we don’t want to) like this:
In all of the scenarios, we would like to remain in the same view hence as we said before – for actual view-switching it’s best to use other solutions. However, it turns out that by default the Frame control will refresh it’s contents even if we’re navigating to the same uri. it will always create the give view from scratch, which is bad because we would probably like to use the same view (and view model) and just change some of the data.
The default behavior of the Frame Control can be extended though:
The Frame Control exposes a Frame.ContentLoader property which accepts type of INavigationContentLoader which we can extend.
The default INavigationContentLoader is of type PageResourceContentLoader, which is what causing the problem – this is the class that is in charge of loading content, and here is where it’s refreshing our views instead of recycling them.
All we need to do is to create an INavigationContentLoader of ourselves, which will recycle views if we’re navigating to the same uri. It’s actually very easy to create since we can copy most of the functionality from PageResourceContentLoader:
1. All of the public methods are methods we are required to implement for INavigationContentLoader.
2. The only interesting methods are BeginLoad, where we check whether the navigation request is for the current view, or for a new one, and saves the result in a private flag, and
3. EndLoad, which, based on the aforementioned flag chooses to either continue and load a new view, or to recycle the current one.
4. The _loader field of type PageResourceContentLoader is what’s doing most of the actual work – we’re actually using 99% of it’s implementation, only deciding for ourselves if we want to recycle the view.
All we need now it to plug it in to the frame, like so:
Our Frame is now ready to be controlled by the NavigationHelper, the only thing that’s missing is to register the Frame with the NavigationHelper, so we should introduce another method to the Navigation Helper, InitializeFrame(Frame theFrame) so we could catch all of the Navigation events and allow us complete control over the frame from the ViewModel.
The initialization of the NavigationHelper should be done in the code behind of the Frame’s UserControl:
And the full code of the NavigationHelper:
And voila – the Navigation is now fully MVVM- enabled
The navigation helper is coupled to the view, but the ViewModels can now hold reference to INavigationHelper, and so be decoupled from it for UnitTesting etc.
Also, the one line of code behind is not pretty, but I don’t think it’s too bad. In any case it’s easy to introduce an attached property that does exactly that, but IMHO it’s an overkill since we’re not going to see more that one line of code behind, in only one “Navigation-Shell” file that we’re not going to touch to much.
Attached is the source code for everything plus 2 views & viewModels that shows it all glued together. It demonstrates very simply how to use it in several scenarios.