向MVVM应用程序添加依赖项注入

本文关键字:注入 依赖 添加 MVVM 应用程序 | 更新日期: 2023-09-27 18:26:17

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

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

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加载到窗口中:

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

这行代码正是我最初使用DI的原因——我无法测试它,因为我无法模拟这里的依赖关系。在这个实例中添加DI对我没有任何帮助,因为我只应该在OnStartUp中使用我的IoC容器,所以我不能使用内核。获取我的QuoteViewModel,对吧?

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

也许更重要的是,这个问题有一个巧妙的解决方案吗?我看到过其他几个例子,它们需要不同的套餐才能发挥作用。现在,感觉MVVM和DI只是不适合彼此打得很好。

向MVVM应用程序添加依赖项注入

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

子视图模型的工厂

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

public interface IVmFactory {
    IQuoteViewModel CreateQuoteViewModel();
}

您可以自己实现这个接口,也可以让NInject。

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

ViewModels.MainViewModel中的正确DI

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

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

DI和MVVM

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

DI显然不是一种反模式,但服务定位器是。不幸的是,服务定位器通常被建议与MVVM一起使用,因为它使您能够快速启动,而不必创建工厂并正确注入它们。权衡是快速开始,而不是拥有一个干净的&可测试的设计-自己决定。

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

[Inject] public IInjector Injector {get; set;}
void MyClassMethod()
{
    var instance = this.Injector.Get<ISomeInterface>();
    // etc
}

这消除了服务位置反模式,还抽象了DI框架的特定于实现的细节。