20
01/12
WP7 Navigation in MVVM
It’s ok. It happens to all of us. One second, you’re just standing there, with C# flying from your fingers like magic sparks, when—BAM– it hits you. You need to navigate to another page. In codebehind, it’s easy, right? You just hit up your navigation service and tell it to navigate, and it just happens for you. But what if you’re using MVVM?
One of the approaches I’ve seen is to pass a navigation service into your view model. I’m not a real fan of this approach, for a couple reasons. First off, unless you use an interface and dependency injection, you’re losing quite a bit of testability in your view model. Also, even if you do, suddenly, your view model needs to know an awful lot about your application, which is something that you want to SOLID-ly avoid.
Jesse Liberty suggests getting access to the navigation service by reaching back into your app to get access to it, like this:
var rootFrame = (App.Current as App).RootFrame; rootFrame.Navigate(new System.Uri("/Views/Page2.xaml", System.UriKind.Relative));
For something quick and dirty, that’s probably fine, but any unit testability is completely gone at that point, as your view model now has a hard dependency on the WP7 application object.
From my standpoint, neither of these options are optimal. My preference is to use messaging to let a global navigation service take over. That way, we can maintain testability, and our view models can be blissfully ignorant of the complexities of navigation. The way I do this is using GalaSoft’s MVVM Light toolkit for Windows Phone.
The first thing you need to do it this way is a global object to handle your navigation requests. I call mine (somewhat unoriginally) “Navigator”:
public class Navigator { private PhoneApplicatoinFrame RootFrame; public Navigator(PhoneApplicationFrame frame) {
RootFrame = frame;
RegisterMessages();
}
private void RegisterMessages()
{
Messenger.Default.Register<ShowTrackerMessage>(this, ShowTracker);
}
private void ShowTracker(ShowTrackerMessage msg)
{
RootFrame.Navigate(new Uri("/Views/ItemLocationCompassView.xaml", UriKind.RelativeOrAbsolute));
}
}
I need this Navigator object to have the same lifetime as my application, so I need to declare it in my App.xaml.cs file:
private static Navigator _navigator; public static Navigator Nav { get { return _navigator; } }
And we need to instantiate it in the constructor of the App, and we need to pass in the application’s RootFrame. I instantiate mine just after the if block that checks if the Debugger is attached:
_navigator = new Navigator(this.RootFrame);
Now, in my view model, when I need to navigate, I can just send a message:
private void ShowTracker() { if (SelectedItem != null) { Messenger.Default.Send(new ShowTrackerMessage()); } }
After that, whenever I need to add new navigation, it’s just a matter of defining a new message, making a quick change to my Navigator to listen for that message, and the appropriate code in my view model to make the call. Easy-peasy.
I’ll leave you with a couple of closing thoughts.
Messages: You have two choices here. You could use a generic message with an enum property to determine the page to which to navigate, or you can have a specific message for each navigation event. I personally choose the individual message route, but it’s probably an equal amount of effort.
Love your code, but never be in love with your code: When I first implemented this, I had a really convoluted way of setting the navigation capabilities in my Navigator class using the main page’s OnNavigatedTo method. After I wrote this article and thought about what Jesse was doing, I found that I was able to use his RootFrame approach and just set everything up in the application’s constructor. It wasn’t until I was completely finished with this article that I figured that approach was better than mine, so I refactored the code, then refactored the article.
I’m still (always) learning: I hope this helped you out, but if you think you have a better way of handling this type of navigation, let me know. I’d love to hear it and discuss the merits of any additional approaches.


