导航逻辑应该放在哪里,视图(View)、视图模型(ViewModel)还是其他地方?

14

我在一个视图上有一个按钮,绑定到ViewModel的ICommand属性(实际上是来自mvvm-light的RelayCommand)

如果用户单击按钮,我想要导航到一个新的视图。当然,NavigationService是视图而不是ViewModel的一部分。这意味着导航是View的责任吗?但在我的情况下,当单击按钮时我将进入的视图取决于许多因素,包括已登录的用户是谁、数据库的状态等等... 视图不应该需要这些信息。

执行NavigationService.Navigate调用的首选选项是什么?

2个回答

13
如果您已经在使用MVVM Light,其中一种选择是利用它包含的消息总线。所以,您可以像您已经做过的那样,在视图模型上将按钮绑定到RelayCommand。在RelayCommand的处理程序中,您可以决定要导航到哪个视图。这将保留所有逻辑在视图模型中。
一旦您的命令处理程序决定要导航到哪个视图,它就可以在消息总线上发布一条消息。您的视图将会监听该消息,然后使用NavigationService来实际执行导航。因此,它除了等待被告知何时导航到某个位置并导航到指示的位置外,不会做任何其他事情。
我通过定义一个NavigationMessage类来实现这一点,我的视图模型可以发布该类,并且我的视图基类继承了包含侦听器的内容。NavigationMessage的样式如下:
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;
    }
}

这样可以简单地传递页面名称,或者还可以选择包括任何必要的查询字符串参数。一个RelayCommand处理程序会像这样发布这条消息:
private void RelayCommandHandler()
{
    //Logic for determining next view, then ...
    Messenger.Default.Send(new NavigationMessage("ViewToNavigate"));
}

最后,视图基类如下所示:
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));
    }
}

假设所有视图都在应用程序根目录下的“Views”文件夹中,这适用于我们的应用程序,但当然可以扩展以支持不同的场景来组织您的视图。


我承认我对MVVM-Light提供的Messenger系统不太熟悉。进一步思考后,这是否意味着所有视图都将注册并听取此NavigationMessage? - Ralph Shillington
我想这可能是个问题。我在一个Windows Phone 7应用程序的上下文中使用这种技术,因此一次只有一个视图处于活动状态,所以它可以完美地工作。如果你正在桌面版的Silverlight或WPF中工作,并且同时有多个视图处于活动状态,我可以看出这可能会成为一个问题。我需要再考虑一下。 - Kevin Kuebler
您可能还想查看Caliburn Micro框架:http://caliburnmicro.codeplex.com/。 它包括一个INavigationService实现,您可以将其注入到视图模型中,类似于Jon在他的答案中描述的内容。 - Kevin Kuebler

7

警告:个人观点的MVVM新手警报 :)(我对MVVM非常新,但到目前为止很喜欢它。)

好问题。我发现,完全可以(尽管有些地方有点丑陋)模拟NavigationService并将INavigationService传递给ViewModel。实际上,您甚至可以使用泛型稍微改进接口,以传递类型(作为类型参数),而不是字符串URI。

然而,当涉及到导航中涉及的额外数据的存储位置时,我发现自己有些困惑...我还没有找到一个好的单一位置来整洁地执行所有编码/解码操作以传播状态。我怀疑ViewModelFactory可能是其中的一部分...

因此,尚不是完美的解决方案-但至少ViewModel可以负责“立即导航”(或“返回”)的操作。


我也很喜欢MVVM,但是似乎Silverlight模板中有一些不太友好的MVVM部分,比如导航。感谢您提出了将类型传递而不是字符串作为“uri”的想法,在这种情况下这是非常合理的。 - Ralph Shillington

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接