将依赖注入添加到 MVVM 应用程序

Adding Dependency Injection to an MVVM application

尝试使用 MVVM 模式回填 WPF 应用程序以使用依赖项注入。我对 DI 不是很熟悉,以前只用过一次,但我想我理解其中的原理。

我需要确保所有绑定都在一个地方注册 - 应用程序根目录。在 WPF 中,这是 OnStartup 方法。因此,我抓取 Ninject 并将其放入我的应用程序中以尝试自动将我的存储库 class 绑定到初始视图:

private void OnStartup(object sender, StartupEventArgs e)
{
    IKernel kernel = new StandardKernel();
    kernel.Bind<IRepository>().To<Repository>();

    Views.MainView view = new Views.MainView();
    view.DataContext = kernel.Get<ViewModels.MainViewModel>();

    view.Show();
}

从现在开始,我使用数据模板资源设置上下文:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:views="clr-namespace:My.Views"
                    xmlns:models="clr-namespace:My.ViewModels" >
    <DataTemplate DataType="{x:Type models:MyViewModel}" >
        <views:MyView />
    </DataTemplate>
    <!-- etc -->
</ResourceDictionary>

并且有效。伟大的!但是,在 MainViewModel 中,我按下一个按钮并将不同类型的 ViewModel 加载到 window:

NavigationHelper.NewWindow(this, new QuoteViewModel(quote, new Repository()));

这行代码正是让我首先进入 DI 的原因 - 我无法对此进行测试,因为我无法在此处模拟出依赖关系。在这种情况下添加 DI 对我一点帮助都没有,因为我只应该在 OnStartUp 中使用我的 IoC 容器,所以我不能使用 kernel.Get 来获取我的 QuoteViewModel,对吗?

四处窥探所以我看到很多人建议我使用服务定位器解决这个问题。这对我来说是新的,我也看到很多人告诉我,将它用于 DI 是一种反模式,不应该被 bargepole 触及。谁是对的?

也许更重要的是,是否有解决此问题的巧妙方法?我见过几个其他示例,这些示例需要不同包的大杂烩才能使其正常工作。现在,感觉 MVVM 和 DI 不能很好地相互配合。

你快到了。你错过了两件事:

子视图模型的工厂

您需要一个能够为您创建子 VM 的工厂。为这个工厂引入一个接口,这样你就可以在测试中替换它。

public interface IVmFactory {
    IQuoteViewModel CreateQuoteViewModel();
}

你可以自己实现这个接口or let NInject do it for you

务必在 DI 容器中注册此工厂,以便容器在收到实例化 类 请求时能够解析它,该工厂作为依赖项。

ViewModels.MainViewModel

中的正确 DI

现在,您可以使用标准构造函数注入将 IVmFactory 注入视图模型:

public class MainViewModel {
    public MainViewModel(IVmFactory vmFactory) {
        _vmFactory = vmFactory;
    }

    // ...
}

DI 和 MVVM

一旦您找到解决 WPF 默认构造函数问题的方法(当您尝试让 WPF 实例化 VM 时),DI 和 MVVM 可以很好地协同工作。

DI 显然不是反模式,but service locator is。不幸的是,服务定位器通常与 MVVM 一起推荐,因为它使您能够快速入门,而无需创建工厂并正确注入它们。权衡是快速开始与拥有干净且可测试的设计 - 自己决定。

我同意服务定位器是一种反模式,我个人通过为 StandardKernel(注入器)创建一个包装器 class 来解决这个问题,它实现了一个接口(IInjector),然后将其自身注入 classes 通过字段注入需要它:

[Inject] public IInjector Injector {get; set;}

void MyClassMethod()
{
    var instance = this.Injector.Get<ISomeInterface>();
    // etc
}

这消除了服务定位反模式,同时也抽象出 DI 框架的具体实现细节。