如何将依赖项注入到MVVM视图模型类中?

8
我熟悉WPF和MVVM模式。现在,我正在尝试在我的新WPF应用程序中使用Autofac实践依赖注入模式。我想知道如何将依赖项注入到MVVM视图模型类中。
如下所示的代码,我有一个根视图模型类MainViewModel,它可以在需要时创建其他视图模型(例如MonitorPageViewModel)实例。在MonitorPageViewModel中,它还需要在需要时创建其他子视图模型(例如MonitorDashboardViewModel)实例。所有这些视图模型可能具有多个依赖项(ILogger、IRepository<RawMessage>、IParsingService等)。
在我的先前的WPF项目中,没有使用任何IoC容器,当需要时我总是在父视图模型中new一个子视图模型,并使用ServiceLocator提供所需的服务。现在,我想找出更多的依赖注入方法来解决这个问题。
我得到了几种方法(在下面的代码中演示),但我对其中任何一种都不满意。
1.使用IoC容器获取响应来创建MainViewModel;并将IoC容器实例注入到MainViewModel中;然后,使用IoC容器实例来解析每个子视图模型构造函数所需的对象。如果子视图模型类需要解析其他类,则将IoC注入其中。[听起来像另一个ServiceLocator]。
2.将MainViewModel及其后代视图模型所需的所有服务注入并沿着视图模型链传递服务。在需要的地方new视图模型实例。[需要注入很多服务,并将它们向下传递]
我不想在这个项目中使用MVVM框架,如Caliburn.Micro。有没有一个简单而优雅的解决方案?
public interface ILogger
{
    // ...
}

public interface IRepository<T>
{
    // ...
}

public interface IStatisticsService
{
    // ...
}

public class RawMessage
{
    // ...
}

public class Device
{
    // ...
}

public class Parser
{
    // ...
}


public interface IParsingService
{
    void Parse(Parser parser);
}

public class DockPaneViewModel : ViewModelBase
{
    // ...
}

public class HomePageViewModel : DockPaneViewModel
{
    public HomePageViewModel(ILogger logger)
    {
        // ...
    }
}

public class MonitorDashboardViewModel : DockPaneViewModel
{
    public MonitorDashboardViewModel(IStatisticsService statisticsService)
    {
        // ...
    }
}

public class MonitorPageViewModel : DockPaneViewModel
{
    public MonitorPageViewModel(ILogger logger, IRepository<RawMessage> repository,
        IRepository<Parser> parserRepository, IParsingService parsingService)
    {
        // ...
    }

    public void CreateDashboard()
    {
        IStatisticsService statisticsService = ??; // how to resolve the service?
        var dashBoardVm = new MonitorDashboardViewModel(statisticsService); // how to create this? 
    }
}

public class ResourceManagementViewModel : DockPaneViewModel
{
    public ResourceManagementViewModel(ILogger logger, IRepository<Device> deviceRepository)
    {
        // ...
    }
}

这里是带有替代构造函数的MainViewModel
public class MainViewModel : ViewModelBase
{
    public ObservableCollection<DockPaneViewModel> DockPanes
    {
        get;
        set;
    } = new ObservableCollection<DockPaneViewModel>();

    #region approach 1
    // use the IOC container take the response to create MainViewModel;
    // and inject the Ioc Container instance to MainViewModel;
    // then, use the IOC container instance to resovle every thing the child view models need
    private readonly ISomeIocContainer _ioc;
    public MainViewModel(ISomeIocContainer ioc)
    {
        _ioc = ioc;
    }

    public void ResetPanes_1()
    {
        DockPanes.Clear();
        DockPanes.Add(new HomePageViewModel(_ioc.Resolve<ILogger>())); // how to new child view model and how to provide the constructor parameters?
        DockPanes.Add(new MonitorPageViewModel(_ioc.Resolve<ILogger>(),
            _ioc.Resolve< IRepository<RawMessage>>(), 
            _ioc.Resolve<IRepository<Parser>>(),
            _ioc.Resolve<IParsingService>())); // also need to inject ISomeIocContainer to MonitorDashboardViewModel for resolve IStatisticsService
        DockPanes.Add(new ResourceManagementViewModel(_ioc.Resolve<ILogger>(), 
            _ioc.Resolve<IRepository<Device>>()));
        // add other panes
    }
    #endregion

    #region approach 2
    // pasing all dependencies of MainViewModel and all descendant View Models in to MainViewModel,
    // and pass dependencies along the ViewModel chain.
    private readonly ILogger _logger;
    private readonly IRepository<RawMessage> _repository;
    private readonly IRepository<Parser> _parserRepository;
    private readonly IRepository<Device> _deviceRepository;
    private readonly IParsingService _parsingService;
    private readonly IStatisticsService _statisticsService;
    public MainViewModel(ILogger logger, IRepository<RawMessage> repository,
        IRepository<Parser> parserRepository, IRepository<Device> deviceRepository,
        IParsingService parsingService, IStatisticsService statisticsService)
    {
        _logger = logger;
        _repository = repository;
        _parserRepository = parserRepository;
         _deviceRepository = deviceRepository;
        _parsingService = parsingService;
        _statisticsService = statisticsService;
    }

    public void ResetPanes_2()
    {
        DockPanes.Clear();
        DockPanes.Add(new HomePageViewModel(_logger)); // how to new child view model and how to provide the constructor parameters?
        DockPanes.Add(new MonitorPageViewModel(_logger, _repository, _parserRepository, _parsingService)); // also need pass statisticsService down 
        DockPanes.Add(new ResourceManagementViewModel(_logger, _deviceRepository));
        // add other panes
    }
    #endregion
}

太多的代码了... - H H
DI和MVVM与View-first方法更配合。您似乎正在使用VM first,我会选择在这里采用Resolve()路线。 - H H
你可以从静态属性中检索容器,而不是将每个视图模型注入其中。否则,我更喜欢第二种方法。注入大量服务意味着视图模型具有许多依赖项。 - mm8
1个回答

12

有时回到基础并保持简单(KISS)的做法往往会奏效。

对于这种情况,我想到的是显式依赖原则纯依赖注入

MainViewModel做了太多的事情,通过注入容器(大忌)或拥有过多的依赖项(代码异味)来表现出来。试图缩小这个类应该做什么的范围(SRP)。

所以,假设主视图模型需要一个窗格集合。那么为什么不给它它所需的东西呢?

public class MainViewModel : ViewModelBase {
    public ObservableCollection<DockPaneViewModel> DockPanes { get; set; }

    //Give the view model only what it needs
    public MainViewModel(IEnumerable<DockPaneViewModel> panes) {
        DockPanes = new ObservableCollection<DockPaneViewModel>(panes);
    }

    public void ResetPanes() {
        foreach (var pane in DockPanes) {
            pane.Reset();
        }
        //notify view
    }
}
注意基础面板略微变化。
public abstract class DockPaneViewModel : ViewModelBase {
    // ...

    public virtual void Reset() {
        //...
    }
}

主视图模型不应该关心依赖项的创建方式,它只需要确保获取其明确要求的内容。

对于不同的面板实现也是如此。

如果一个视图模型需要能够创建多个子元素,则将该责任委托给工厂。

public class MonitorPageViewModel : DockPaneViewModel {
    public MonitorPageViewModel(ILogger logger, IRepository<RawMessage> repository,
        IRepository<Parser> parserRepository, IParsingService parsingService, 
        IPaneFactory factory) {
        // ...
    }

    public void CreateDashboard() {
        var dashBoardVm = factory.Create<MonitorDashboardViewModel>();
        
        //...
    }
}

主题应该尽可能少承担责任。

以视图模型优先或视图优先的方式被认为是实现关注点,如果遵循惯例优于配置模型,则实际上并不重要。

如果设计得好,使用框架还是纯代码都无关紧要。

当把所有东西组合在一起时,这些框架确实非常方便。最简单和优雅的解决方案是有某个东西创建对象图,但是没有这个东西,您只能在组合根中自己构建它。


1
你提到应该使用框架来将所有东西组合在一起。但是怎么做呢?假设我有一个 ViewModel 的层次结构,只有底部的子级需要与用户通信的消息服务。为了进行测试,我想将此服务注入到子级中。如果没有 ServiceLocator,没有传递容器并且不向父 ViewModel 添加它们甚至不需要的依赖项,我该如何访问该服务? - user2727133

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