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

Posted

技术标签:

【中文标题】WPF、MVVM、导航、保持依赖注入完整【英文标题】:WPF, MVVM, Navigation, Keeping Dependency Injection In-Tact 【发布时间】:2015-10-22 15:53:06 【问题描述】:

我有一个简单的 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;
        
    

然后我有一个UserControlHome,我想“导航”到主窗口(即设置ContentControl。它的构造函数也通过构造函数注入以相同的方式接收一个 ViewModel Main确实如此。同样简单:

public Home(HomeViewModel homeViewModel)

    InitializeComponent();

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

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

我是MVVM的View-first阵营,关于它你可以找到一个很好的discussion in these comments。

我已经看到一些关于基于导航的服务的想法的典故;但是,我不确定这是否能保持 MVVM 所追求的关注点分离。 DataTemplates 需要不带参数的 View 构造函数,我读过对 DataTemplates 的批评,认为 ViewModels 不应参与 View 的实例化。

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

维护对Main 视图的全局、类似单例的引用的导航服务是否最有意义? Main 视图是否可以公开方法,例如:

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

然后由该服务访问?

【问题讨论】:

虽然您说您处于“视图优先阵营”,但您尝试解决的确切场景在 VM 优先方法中很容易处理。使用您的 DI 容器构建您的视图模型图,并让 DataTemplates 处理接线。 @AndrewHanlon 那是我在一秒钟前刚刚做出的改变(我仍在努力确定每一个的含义——我想我实际上是在 VM-first 阵营)。你能给我举个例子吗? 哦,等等。在这种情况下,除了 DataTemplate 之外,View 甚至都不知道 ViewModel,对吗?如果我使用 DI 解决依赖关系,Main 如何知道要显示哪个视图?即依赖项必须存在。使用像 Unity 这样的框架,我无法从 IoC 中获取内容(我也不想这样做) 因此,在 VM-first 中,View 知道 VM(或 VM 合约接口),但没有直接注入 VM,只能通过 DataContext,它是通过 ContentControl 自动设置的. 查看Here的方法7/8。至于初始化,只需更改app.xml,这样您的主视图就不会自动创建。然后您可以执行所有 VM 初始化 (DI),然后创建 Main 并设置其 DataContext。在此之后,让 DataTemplates 为您完成接线。 【参考方案1】:

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

无需容器的解决方案

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

一般来说,对 Rachel 方法的最大反对意见是 View Model 然后负责——或者,“定义”——它自己与 View 的关系。对于 Lim 的解决方案,这是一个次要的反对意见,原因有两个(并且没有进一步证明其他 糟糕 架构决策的合理性,稍后将描述):

1.) DataTemplate 关系不是由 XAML 文件以外的任何东西强制执行的,即视图模型本身永远不会直接知道它们的视图,反之亦然,因此,即使我们 View 的构造函数被进一步简化,例如 Home 类构造函数 - 现在不需要引用 View Model:

public Home()

    InitializeComponent();

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

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

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

依赖注入

应用程序的设计应完全不依赖于其容器,甚至更好的是 - 任何有关服务依赖关系的特定于实现的信息。这使我们能够做的是将通过特定合同(接口)强制执行的服务“分配”(注入)到构成我们应用程序功能核心的各种类。

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

    应用程序类应该能够支持执行相同任务的各种服务,只要它们遵守所描述的合同。 应用程序类应该从不知道或引用它们的容器,即使容器是 Unity、PRISM 或 Galasoft。

第二点是最重要的,是一个经常被打破的“规则”。那个“打破规则”是原始帖子背后的灵感。

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

关于视图、视图模型或模型部分的依赖解析容器的任何知识都是反模式(您可以在其他地方阅读有关该声明的理由的更多信息)。

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

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

我们应该留下如下构造函数:

// View:
public Home()  //...  

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

如果一个目标是模块化和可重用性,那么上述实现了,很好。我们可以通过确保那些传入的依赖项是通过接口实现的合同来继续进一步抽象这一点。

【讨论】:

你好,托马斯。您的主题和与@Andrew Hanlon 的讨论对我有很大帮助。您能否提供“Lim 的方法”(链接、代码等)以供进一步阅读? @dios231 Navigation with MVVM 应该是一个超链接。尝试点击它。如果这不起作用,请告诉我。如果没有,我会尝试充实上述内容。 再次问好 Thomas,感谢您在如此古老的帖子中的回答。一个问题。在ApplicationViewModel 构造函数的Rachel 文章中,有一个包含所有应用程序视图模型的列表。你认为这是一种不好的做法吗?由于应用程序引导程序中没有组合根部分,您如何使用 DI? @dios231 我从来没有真正确定我对实例化视图模型列表的感受。我最终对这种方法有两个担忧:如果我有一个大型应用程序,内存中列表的大小以及维护列表以准确反映应用程序的状态,例如ViewModel 的生命周期。我不确定我是否理解您关于 DI 的问题。 我和你有同样的担忧。问题是这个列表似乎是 Lim 解决方案的关键部分。 ViewModel 工厂似乎也不是解决方案,因为它必须了解 ViewModels 依赖关系,这很尴尬。当我提出解决方案时,我会通知您。谢谢!!!

以上是关于WPF、MVVM、导航、保持依赖注入完整的主要内容,如果未能解决你的问题,请参考以下文章

如何在 WPF/MVVM 应用程序中处理依赖注入

.NET6+WPFWPF使用prism框架+Unity IOC容器实现MVVM双向绑定和依赖注入

将 DbContext 与依赖注入一起使用

uwp 与 c# 和 mvvm 如何做控制反转(依赖注入)

使用 Unity 进行 MVVM 依赖注入,用于分层视图模型

如何在 Room MVVM 架构中实现 Koin 依赖注入