In MVVM applications, it can be a bit of a challenge to send instructions to the view (for example a page) from a viewmodel. Thankfully, we have good tools at our disposal to help with that.
In his excellent series “MVVM Light Toolkit soup to nuts”, Jesse Liberty proposes one approach using the MVVM Light messaging infrastructure. While this works fine, I would like to show here another approach using what I call a “view service”, i.e. an abstracted service that is invoked from the viewmodel, and implemented on the view.
Multiple kinds of view services
In fact, I use view services quite often, and even started standardizing them for the Windows Phone 7 applications I work on. If there is interest, I will be happy to show other such view services, for example
- Animation services, responsible to start/stop animations on the view.
- Dialog service, in charge of displaying messages to the user and gathering feedback.
- Navigation service, in charge of navigating to a given page directly from the viewmodel.
In this article, I will concentrate on the navigation service.
The INavigationService interface
In most WP7 apps, the navigation service is used in quite a straightforward way. We want to:
- Navigate to a given URI.
- Go back.
- Be notified when a navigation is taking place, and be able to cancel.
The INavigationService interface is quite simple indeed:
1.
public
interface
INavigationService
2.
{
3.
event
NavigatingCancelEventHandler Navigating;
4.
void
NavigateTo(Uri pageUri);
5.
void
GoBack();
6.
}
Obviously, this interface can be extended if necessary, but in most of the apps I worked on, I found that this covers my needs.
The NavigationService class
It is possible to nicely pack the navigation service into its own class. To do this, we need to remember that all the PhoneApplicationPage instances use the same instance of the navigation service, exposed through their NavigationService property. In fact, in a WP7 application, it is the main frame (RootFrame, of type PhoneApplicationFrame) that is responsible for this task. So, our implementation of the NavigationService class can leverage this.
First the class will grab the PhoneApplicationFrame and store a reference to it. Also, it registers a handler for the Navigating event, and forwards the event to the listening viewmodels (if any).
Then, the NavigateTo and the GoBack methods are implemented. They are quite simple, because they are in fact just a gateway to the PhoneApplicationFrame.
The whole class is as follows:
01.
public
class
NavigationService : INavigationService
02.
{
03.
private
PhoneApplicationFrame _mainFrame;
04.
05.
public
event
NavigatingCancelEventHandler Navigating;
06.
07.
public
void
NavigateTo(Uri pageUri)
08.
{
09.
if
(EnsureMainFrame())
10.
{
11.
_mainFrame.Navigate(pageUri);
12.
}
13.
}
14.
15.
public
void
GoBack()
16.
{
17.
if
(EnsureMainFrame()
18.
&& _mainFrame.CanGoBack)
19.
{
20.
_mainFrame.GoBack();
21.
}
22.
}
23.
24.
private
bool
EnsureMainFrame()
25.
{
26.
if
(_mainFrame !=
null
)
27.
{
28.
return
true
;
29.
}
30.
31.
_mainFrame = Application.Current.RootVisual
as
PhoneApplicationFrame;
32.
33.
if
(_mainFrame !=
null
)
34.
{
35.
// Could be null if the app runs inside a design tool
36.
_mainFrame.Navigating += (s, e) =>
37.
{
38.
if
(Navigating !=
null
)
39.
{
40.
Navigating(s, e);
41.
}
42.
};
43.
44.
return
true
;
45.
}
46.
47.
return
false
;
48.
}
49.
}
Exposing URIs
I find that it is a good practice to expose each page’s URI as a constant. In MVVM Light applications, a good place to do that is the ViewModelLocator, which already acts like a central point of setup for the views and their viewmodels.
Note that in some cases, it is necessary to expose the URL as a string, for instance when a query string needs to be passed to the view. So for example we could have:
1.
public
static
readonly
Uri MainPageUri =
new
Uri(
"/MainPage.xaml"
, UriKind.Relative);
2.
public
const
string
AnotherPageUrl =
"/AnotherPage.xaml?param1={0}¶m2={1}"
;
Creating and using the NavigationService
Normally, we only need one instance of the NavigationService class. In cases where you use an IOC container, it is easy to simply register a singleton instance. For example, I am using a modified version of a super simple IOC container, and so I can register the navigation service as follows:
1.
SimpleIoc.Register<INavigationService, NavigationService>();
Then, it can be resolved where needed with:
1.
SimpleIoc.Resolve<INavigationService>();
Or (more frequently), I simply declare a parameter on the viewmodel constructor of type INavigationService and let the IOC container do its magic and inject the instance of the NavigationService when the viewmodel is created.
On supported platforms (for example Silverlight 4), it is also possible to use MEF. Or, of course, we can simply instantiate the NavigationService in the ViewModelLocator, and pass this instance as a parameter of the viewmodels’ constructor, injected as a property, etc…
Once the instance has been passed to the viewmodel, it can be used, for example with:
1.
NavigationService.NavigateTo(ViewModelLocator.ComparisonPageUri);
Testing
Thanks to the INavigationService interface, navigation can be mocked and tested when the viewmodel is put under unit test. Simply implement and inject a mock class, and assert that the methods are called as they should by the viewmodel.
Conclusion
As usual, there are multiple ways to code a solution answering your needs. I find that view services are a really neat way to delegate view-specific responsibilities such as animation, dialogs and of course navigation to other classes through an abstracted interface. In some cases, such as the NavigationService class exposed here, it is even possible to standardize the implementation and pack it in a class library for reuse. I hope that this sample is useful!
Happy coding.
Laurent