WPF,MVVM,导航,保持依赖注入完整性

4
我是一名有帮助的助手,我将为您翻译编程相关内容。以下是需要翻译的文本:

我有一个简单的WPF应用程序,它使用Unity框架进行依赖注入。目前,我正在尝试简化我的MVVM模式实现中视图之间导航的方法;然而,Stack Overflow上的许多示例都没有考虑到依赖注入的注意事项。

我有两个完全分开的视图。

其中一个Main作为主窗口,加载内容(非常典型;消除了不必要的内容):

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

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

        public Main (MainViewModel mainViewModel)
        {
            InitializeComponent();

           this.DataContext = _mainViewModel = mainViewModel;
        }
    }

我有一个UserControl,名为Home,我想要在主窗口中“导航”到它(即设置ContentControl)。它的构造函数也通过构造函数注入接收ViewModel,就像Main一样简单:

public Home(HomeViewModel homeViewModel)
{
    InitializeComponent();

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

主要问题在于我想启用基于构造函数的注入,同时尽可能保持纯净的MVVM实现。
我属于MVVM中的View-first派别,在这些评论中,你可以找到对此进行讨论的良好资源
我看到了一些关于基于导航的服务的暗示; 然而,我不确定它是否保持了MVVM所追求的关注点分离。 DataTemplates需要不带参数的View构造函数,并且我已经阅读了有关DataTemplates的批评,认为ViewModels不应参与Views的实例化。 这个解决方案(依我之见)是完全错误的,因为ViewModel意识到了它的View,并依赖于一个服务来实例化ViewModel,这使得真正的依赖注入来解决ViewModel和View依赖关系变得几乎不可能。 在这个MSDN文章中,使用RelayCommand时这个问题非常明显。

一个维护全局、类似单例的引用到 Main 视图的导航服务是最合理的吗?
Main 视图暴露一个方法,例如:

public void SetContent(UserControl userControl) { //... }

那么它是由该服务访问的吗?


虽然你说你是“视图优先派”的,但你试图解决的确切场景在“视图模型优先派”方法中很容易且简单地处理。使用你的 DI 容器来构建你的视图模型图,并让数据模板处理连线。 - Andrew Hanlon
@AndrewHanlon 那是我刚刚做出的更改(我仍在尝试决定每个意义 - 而且我认为我实际上是VM-first阵营)。你能给我指一下一个例子吗? - Thomas
哦,等等。在这种情况下,除了DataTemplate之外,View甚至不会意识到ViewModel的存在,对吗?那么,如果我使用DI来解析依赖项,Main如何知道要显示哪个View呢?也就是说,依赖项必须存在。使用像Unity这样的框架,我无法从IoC中获取东西(我也不想这样做)。 - Thomas
1
因此,在VM-first中,View知道VM(或VM合同接口),但没有直接注入VM,只能通过DataContext进行注入,这是通过ContentControl自动设置的。 - Andrew Hanlon
1
这里看看第7/8种方法。至于初始化,只需更改app.xml文件,使其不自动创建主视图。然后,您可以进行所有VM初始化(DI),然后创建一个Main并设置其DataContext。完成后让DataTemplates为您进行连线。 - Andrew Hanlon
显示剩余2条评论
1个回答

2
这是我对另一位作者提供的解决方案实现动机的阐述。由于链接的文章提供了很好的代码示例,因此我不提供代码。当然,这些是我的观点,但也代表了我对该主题的研究的综合。

无需容器的解决方案

Rachel Lim写了一篇很棒的文章,使用MVVM进行导航,描述了如何充分利用WPF的DataTemplate来解决MVVM导航所面临的挑战。Lim的方法提供了“最佳”解决方案,因为它“极大地减少了任何框架依赖的需要”;然而,解决问题确实没有什么“好”的方法。

总的来说,对Rachel的方法最大的反对意见是,视图模型随后变得负责 - 或者说,“定义” - 它自己与视图的关系。对于Lim的解决方案,这是一个小的反对意见,原因有两个(并不能进一步证明其他“糟糕”的架构决策,稍后会描述):

1.)

DataTemplate关系除了XAML文件外没有其他的强制规定,也就是说,视图模型本身从来不直接知道它们的视图,反之亦然,因此,即使我们的视图构造函数更加简化,例如Home类构造函数 - 现在无需引用视图模型:

public Home()
{
    InitializeComponent();
}

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

一个应用程序应该能够在没有预定义视图的情况下充分发挥功能(对领域进行建模)。这个理想源于努力最好地解耦应用程序的支持架构,并促进SOLID编程原则的进一步应用,尤其是依赖注入。

XAML文件 - 而不是第三方依赖容器 - 成为解析视图和视图模型之间关系的关键点(因此,直接与OP相矛盾)。

依赖注入

一个应用程序应该被设计成完全不知道它的容器,甚至更好的是不知道任何实现特定信息关于服务依赖。这使我们能够将遵守某个协议(接口)的服务“分配”(注入)到组成应用程序功能核心的各种类中。

这给我们留下了两个“良好设计”的标准:

  1. 只要它们遵守所描述的契约,应用程序类应该能够支持执行相同任务的各种服务。
  2. 即使容器是Unity、PRISM或Galasoft,应用程序类也不应该知道或引用它们的容器。
第二点非常重要,是一个经常被违反的“规则”。这种“规则破坏”是原始帖子的灵感来源。
在许多应用程序中,对导航问题的响应是注入一个包装的依赖注入容器,然后使用它来从实现类调用解析依赖项。现在,该类知道容器的存在,更糟糕的是,它甚至具有更具体的知识,需要执行其操作的特定内容(有些人可能会认为这更难以维护)。
视图、视图模型或模型对依赖项解析容器的任何了解都是一种反模式(您可以在其他地方阅读更多关于该语句的正当理由)。
依赖注入的良好编写的应用程序可以在没有依赖注入框架的情况下运行,即您可以手动从手写引导程序中解析依赖项(尽管这需要大量仔细的工作)。
Lim的解决方案使我们能够“不需要”从实现中引用容器。
我们应该留下的是看起来像这样的构造函数:
// View:
public Home() { //... } 

// View Model 
public HomeViewModel (SomeModelClass someModel, MaybeAService someService)

如果一个目标是模块化和可重用性,那么上述方法实现得很好。我们可以通过确保传入的依赖关系是通过接口实现的契约履行来进一步抽象化。

你好Thomas。你的话题和与@Andrew Hanlon的讨论对我很有帮助。你能提供“Lim's approach”(链接、代码等)以供进一步阅读吗? - dios231
@dios231,“使用MVVM进行导航”应该是一个超链接。试着点击它。如果不行,请告诉我。如果需要,我会尽力详细说明上述内容。 - Thomas
再次你好,Thomas,感谢你在那么久之前的帖子中的回答。有一个问题。在Rachel的ApplicationViewModel文章中,构造函数中有一个包含所有应用程序ViewModel的列表。你认为这是一种不好的做法吗?由于应用程序引导部分没有组合根部分,你如何使用依赖注入(DI)呢? - dios231
@dios231 我一直不确定我对实例化ViewModel列表的看法。我最终有两个担忧:如果我有一个大型应用程序,列表在内存中的大小以及维护列表以准确反映应用程序状态(例如ViewModel的生命周期)。我不确定我理解你关于DI的问题。 - Thomas
我和你有同样的担忧。问题在于这个列表似乎是Lim解决方案的关键部分。此外,ViewModel工厂似乎也不是解决方案,因为它必须知道ViewModel的依赖关系,这非常尴尬。当我想出一个解决方案时,我会通知你的。谢谢! - dios231

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