如何将依赖项注入 MVVM 视图模型 class?
How to inject dependency into MVVM View Model class?
我熟悉 WPF 和 MVVM 模式。现在,我正在尝试使用 Autofac 在我的新 WPF 应用程序中练习依赖注入模式。我想弄清楚如何将依赖项注入 MVVM 视图模型 class。
作为下面的代码,我有一个根视图模型class MainViewModel
,它可以在需要时创建其他视图模型(例如MonitorPageViewModel
)实例。在MonitorPageViewModel
中,需要时还需要创建其他子视图模型(如MonitorDashboardViewModel
)实例。所有这些视图模型可能有几个依赖项(ILogger
、IRepository<RawMessage>
、IParsingService
等)。
在我以前的没有使用任何 IoC 容器的 WPF 项目中,我总是在需要时 new
在父视图模型中创建一个子视图模型,并使用 ServiceLocator 来提供所需的服务。现在,我想找出一种更依赖注入的方式来做到这一点。
我有几种方法(在下面的代码中演示),但我对其中任何一种都不满意。
- 使用 IoC 容器获取响应来创建
MainViewModel
;并将 IoC Container 实例注入 MainViewModel
;然后,使用 IoC 容器实例来解析子视图模型构造函数需要的每个对象。如果子视图模型 class 需要解析其他 class,则将 IoC 注入其中。 [听起来是另一个 ServiceLocator]。
- 注入
MainViewModel
及其后代视图模型所需的所有服务,并沿着视图模型链传递服务。 new
视图模型实例位于需要的位置。 [需要注入很多服务,并向下传递]
我不想在这个项目中使用 Caliburn.Micro
等 MVVM 框架。有没有简单而优雅的解决方案?
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
}
有时回到基础并保持简单 (KISS) 往往会奏效。
想到这个场景的是The Explicit Dependency Principle and Pure Dependency Injection。
MainViewModel
通过注入容器(大不,不)或有很多依赖项(代码味道)来明显做的太多了。尝试缩小 class 应该做的事情 (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>();
//...
}
}
同样,主题应该承担尽可能少的责任。
View First 或 ViewModel First 被认为是实现问题,如果遵循配置模型的约定,则实际上无关紧要。
如果设计的好,用框架还是纯代码真的无所谓
虽然这些框架在将所有内容放在一起时确实派上用场。最简单优雅的解决方案是创建对象图,但如果没有它,您只能在组合根中自行构建它。
我熟悉 WPF 和 MVVM 模式。现在,我正在尝试使用 Autofac 在我的新 WPF 应用程序中练习依赖注入模式。我想弄清楚如何将依赖项注入 MVVM 视图模型 class。
作为下面的代码,我有一个根视图模型class MainViewModel
,它可以在需要时创建其他视图模型(例如MonitorPageViewModel
)实例。在MonitorPageViewModel
中,需要时还需要创建其他子视图模型(如MonitorDashboardViewModel
)实例。所有这些视图模型可能有几个依赖项(ILogger
、IRepository<RawMessage>
、IParsingService
等)。
在我以前的没有使用任何 IoC 容器的 WPF 项目中,我总是在需要时 new
在父视图模型中创建一个子视图模型,并使用 ServiceLocator 来提供所需的服务。现在,我想找出一种更依赖注入的方式来做到这一点。
我有几种方法(在下面的代码中演示),但我对其中任何一种都不满意。
- 使用 IoC 容器获取响应来创建
MainViewModel
;并将 IoC Container 实例注入MainViewModel
;然后,使用 IoC 容器实例来解析子视图模型构造函数需要的每个对象。如果子视图模型 class 需要解析其他 class,则将 IoC 注入其中。 [听起来是另一个 ServiceLocator]。 - 注入
MainViewModel
及其后代视图模型所需的所有服务,并沿着视图模型链传递服务。new
视图模型实例位于需要的位置。 [需要注入很多服务,并向下传递]
我不想在这个项目中使用 Caliburn.Micro
等 MVVM 框架。有没有简单而优雅的解决方案?
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
}
有时回到基础并保持简单 (KISS) 往往会奏效。
想到这个场景的是The Explicit Dependency Principle and Pure Dependency Injection。
MainViewModel
通过注入容器(大不,不)或有很多依赖项(代码味道)来明显做的太多了。尝试缩小 class 应该做的事情 (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>();
//...
}
}
同样,主题应该承担尽可能少的责任。
View First 或 ViewModel First 被认为是实现问题,如果遵循配置模型的约定,则实际上无关紧要。
如果设计的好,用框架还是纯代码真的无所谓
虽然这些框架在将所有内容放在一起时确实派上用场。最简单优雅的解决方案是创建对象图,但如果没有它,您只能在组合根中自行构建它。