我有一个简单的WPF应用程序,它利用Unity框架进行依赖注入(inject)。当前,我正在尝试简化MVVM模式实现中的 View 之间导航方法。但是,整个堆栈溢出中的许多示例都没有考虑依赖注入(inject)的警告。

我有两种完全不同的看法。

第一,Main充当将内容加载到其中的主窗口(非常典型;消除了不必要的内容):

<Window x:Class="Application.UI.Main">
    <Grid Background="White">
        <ContentControl Content="{Binding aProperty}"/>
    </Grid>
</Window>

构造函数通过构造函数注入(inject)接收一个ViewModel(再次非常简单):
    public partial class Main
    {
        private MainViewModel _mainViewModel;

        public Main (MainViewModel mainViewModel)
        {
            InitializeComponent();

           this.DataContext = _mainViewModel = mainViewModel;
        }
    }

然后,我有一个UserControlHome,我想在其上“导航”主窗口(即设置ContentControl。它的构造函数还通过构造函数注入(inject)接收一个ViewModel,与Main一样。同样简单:
public Home(HomeViewModel homeViewModel)
{
    InitializeComponent();

    // Set Data Context:
    this.DataContext = homeViewModel;
}

这里的主要问题是,我想启用基于构造函数的注入(inject),同时保持尽可能纯的MVVM实现。

我在MVVM的“查看优先”阵营中,您可以在其中找到一个不错的discussion in these comments

我已经看到了基于导航的服务的一些暗示。但是,不确定是否可以保持MVVM所追求的关注点分离。 DataTemplates要求不带参数的View构造函数,而我已阅读了对DataTemplates的批评,该批评认为ViewModels不应参与View的实例化。

This solution(在我看来)是完全错误的,因为ViewModel意识到了自己的View并依赖于ViewModel实例化服务,这使得真正的依赖注入(inject)可以解析ViewModel和View依赖,但几乎是不可能的。这个问题在MSDN article中使用RelayCommand时非常明显。

维护对Main View 的全局,单例引用的导航服务是否最有意义? Main View 公开一个方法是否可以接受,例如:
public void SetContent(UserControl userControl) { //... }

那该服务可以访问吗?

最佳答案

这是我实现另一作者提供的解决方案背后的动机。我不提供代码,因为链接文章提供了很棒的代码示例。这些当然是我的观点,但也代表了我对该主题的研究的结合

无需容器的解决方案

Rachel Lim撰写了一篇很棒的文章Navigation with MVVM,描述了如何充分利用WPF的DataTemplate来解决MVVM导航所带来的挑战。 Lim的方法提供了“最佳”解决方案,因为它大大减少了对任何Framework依赖项的需求。但是,确实没有解决问题的“好方法”。

通常,对拉结的方法最大的反对意见是, View 模型将负责(或“定义”)它与 View 的关系。对于Lim的解决方案,这是次要的反对,其原因有两个(不作任何进一步证明其他不良体系结构决策的理由,如下所述):

1.)DataTemplate关系不是由XAML文件强制执行的,也就是说, View 模型本身永远不会直接知道其 View ,反之亦然,因此,即使我们的View构造函数也得到了进一步简化,例如Home类构造函数-现在无需引用 View 模型:

public Home()
{
    InitializeComponent();
}

2.)由于没有其他地方表达这种关系,因此特定 View 和 View 模型之间的关联很容易更改。

一个应用程序应该能够在没有指定的View的情况下充分运行(建模域)。这种理想源自于努力使应用程序的支持体系结构最佳地分离,并促进SOLID编程原理(尤其是依赖注入(inject))的进一步应用。

XAML文件(不是第三方依赖关系容器)成为解决View和View Model之间关系的关键点(因此,它与OP直接矛盾)。

依赖注入(inject)

应将应用程序设计为完全不了解其容器,甚至更好的是,有关服务依赖关系的任何特定于实现的信息。这允许我们做的是将通过某种契约(Contract)(接口(interface))强制执行的服务“分配”(注入(inject))到组成我们应用程序功能的各个类。

这给我们留出了两个“良好设计”的标准:
  • 应用程序类只要能够遵守所描述的契约(Contract),就应该能够支持执行相同任务的各种服务。
  • 即使容器是Unity,PRISM或Galasoft,应用程序类也不应该知道或引用其容器。

  • 第二点是最重要的,通常是被打破的“规则”。该“打破规则”是原始帖子背后的灵感。

    在许多应用程序中,对导航问题的响应是注入(inject)一个包装的依赖项注入(inject)容器,然后将其用于从实现类中进行调用以解决依赖关系。该类(class)现在了解容器,更糟糕的是,它对执行操作所需的详细信息有了更多,更具体的了解(有些人可能认为更难维护)。

    对View,View Model或Model方面的依赖关系解决方案容器的任何了解都是一种反模式(您可以在其他地方阅读更多有关该声明的理由)。

    一个依赖于依赖注入(inject)的编写良好的应用程序可以在没有依赖注入(inject)框架的情况下运行,即您可以从手写 bootstrap 手动解决依赖关系(尽管这需要大量的精心工作)。

    Lim的解决方案使我们能够从实现中“不需要”对容器的引用。

    我们应该留给的是类似如下的构造函数:
    // View:
    public Home() { //... }
    
    // View Model
    public HomeViewModel (SomeModelClass someModel, MaybeAService someService)
    

    如果一个目标是模块化和可重用性,那么上面就可以很好地实现这一目标。我们可以通过确保那些传入的依赖关系是通过接口(interface)的契约(Contract)履行,来继续进行抽象。

    10-07 19:18
    查看更多