views:

165

answers:

2

I a button in a view, bound to an ICommand property of the ViewModel (actually it's RelayCommand from mvvv-light)

If the user clicks on the button I want to navigate to a new view. Of course the NavigationService is part of the View not the ViewModel. That implies that the navigation is the responsibility of the View? But in my case, the view I will be going when the button is clicked depends on a great many factors, including who the logged in user is, the state the database is in, etc... Surely the View shouldn't need all that information.

What is the preferred option for executing a NavigationService.Navigate call?

A: 

Warning: opinionated MVVM newbie alert :) (I'm very new to MVVM, but enjoying it a lot so far.)

Good question. I've found that it's perfectly feasible (if a little ugly in places) to mock out NavigationService and pass an INavigationService to a ViewModel. In fact, you can even make the interface slightly nicer with generics, to pass in a type (as a type argument) rather than a string URI.

However, I've found I've come somewhat unstuck when it comes to where you put the extra data involved in navigation... I haven't found a good single place to do all the encoding/unencoding to propagate state neatly. I suspect the ViewModelFactory may well be part of that equation...

So, not a perfect solution yet - but at least ViewModel can be responsible for the action of "navigate now" (or "go back").

Jon Skeet
@Jon, I too am enjoying MVVM, howerver there does seem to be some bits of the Silverlight templates that aren't particularily MVVM friendly; navigation being one. Thanks for the idea of passing a type rather than a string as the "uri". that makes perfect sense in this context.
Ralph Shillington
+1  A: 

If you're already using MVVM Light, one option is to make use of the message bus that it includes. So you bind your button to a RelayCommand on the view model, as you've said you're already doing. In the handler for your RelayCommand you can make the decision on which view to navigate to. This keeps all that logic in the view model.

Once your command handler has decided which view to navigate to, it can publish a message on the message bus. Your view will be listening for that message and then use the NavigationService to actually perform the navigation. So it's not doing anything other than waiting to be told to navigate somewhere and then navigating where it's told.

I've been doing this by defining a NavigationMessage class that my view models can publish, and a view base class that my views inherit from which contains the listener. The NavigationMessage looks like this:

public class NavigationMessage : NotificationMessage
{
    public string PageName
    {
        get { return base.Notification; }
    }

    public Dictionary<string, string> QueryStringParams { get; private set; }

    public NavigationMessage(string pageName) : base(pageName) { }

    public NavigationMessage(string pageName, Dictionary<string, string> queryStringParams) : this(pageName)
    {
        QueryStringParams = queryStringParams;
    }
}

This allows for simply passing the page name, or optionally also including any necessary query string parameters. A RelayCommand handler would publish this message like this:

private void RelayCommandHandler()
{
    //Logic for determining next view, then ...
    Messenger.Default.Send(new NavigationMessage("ViewToNavigate"));
}

Finally, the view base class looks like this:

public class BasePage : PhoneApplicationPage
{
    public BasePage()
    {
        Messenger.Default.Register<NavigationMessage>(this, NavigateToPage);
    }

    protected void NavigateToPage(NavigationMessage message)
    {
        //GetQueryString isn't shown, but is simply a helper method for formatting the query string from the dictionary
        string queryStringParams = message.QueryStringParams == null ? "" : GetQueryString(message);

        string uri = string.Format("/Views/{0}.xaml{1}", message.PageName, queryStringParams);
        NavigationService.Navigate(new Uri(uri, UriKind.Relative));
    }
}

This is assuming a convention where all the views are in a "Views" folder in the root of the app. This works fine for our app but of course this could be extended to support different scenarios for how you organize your views.

Kevin Kuebler
Excellent suggestion, thanks!
Ralph Shillington
I will admit I'm not too familiar with the the Messenger system that MVVM-Light offers.Upon further reflection, does this not mean that all the views will register for and hear this NavigationMessage?
Ralph Shillington
I suppose that could be an issue. I'm using this technique in the context of a Windows Phone 7 app where I only have one view active at a time so it works perfectly well. If you're working in the desktop version of Silverlight, or WPF, and you have multiple views active at once I can see where this might be a problem. Will have to think about that some more.
Kevin Kuebler
You may also want to check out the Caliburn Micro framework: http://caliburnmicro.codeplex.com/. It includes an INavigationService implementation that you can inject into your view models, similar to what Jon described in his answer.
Kevin Kuebler