在MVVM WPF应用程序中实现模式对话框时,使用DependencyInjection替换ServiceLocator

kaj*_*ajo 5 c# dependency-injection modal-dialog mvvm

我正在编写我的第一个WPF应用程序,我想请求您帮助解决我遇到的问题.

我试图遵循MVVM模式,我来到了需要实现模态对话框的地步.我用谷歌搜索/阅读了一段时间,我能够找到解决方案.然而,当重构时,我遇到了一个困境,即使用DI(构造函数注入)作为服务定位器的替代.

我将参考这些:http://pastebin.com/S6xNjtWW.

我非常喜欢Roboblob的方法:

第一:他创建了一个模态对话框(界面)的抽象.我命名接口IModalDialog,它是这样的:

public interface IModalDialog
{
    bool? DialogResult { get; set; }
    object DataContext { get; set; }

    void Show();
    bool? ShowDialog();
    void Close();        

    event EventHandler Closed;
}
Run Code Online (Sandbox Code Playgroud)

第二:模态对话服务的抽象:

public interface IModalDialogService
{       
    void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel, Action<TDialogViewModel> onDialogClose) where TDialogViewModel : class;        
    void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel) where TDialogViewModel : class;        
}
Run Code Online (Sandbox Code Playgroud)

第三: IModalDialogService的具体实现:

    public class ModalDialogService : IModalDialogService
    {
    public void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel, Action<TDialogViewModel> onDialogClose) where TDialogViewModel : class
    {
        // set datacontext
        if (viewModel != null)
        {
            view.DataContext = viewModel;
        }
        ((System.Windows.Window)view).Owner = System.Windows.Application.Current.MainWindow;
        // register 
        if (onDialogClose != null)
        {
            view.Closed += (sender, e) => onDialogClose(viewModel);
        }
        view.ShowDialog();
    }


    public void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel) where TDialogViewModel : class
    {
        this.ShowDialog(view, viewModel, null);
    }
Run Code Online (Sandbox Code Playgroud)

第四: IModalDialog有更多的实现.每个都是一个实现IModalDialog的Window派生类.


在我提出问题(描述问题)之前,我需要事先解释一下:

假设我有更多服务,例如IMessageBoxService.然后我需要在MainWindowViewModel的构造函数中声明这些依赖项:

public MainWindowViewModel(IModalDialogService a,                               
                           IMessageBoxService b,
                           ...)
Run Code Online (Sandbox Code Playgroud)

所以我可以注射它们(手动或使用像Unity这样的IOC容器).

为了能够使用模态对话服务,有一个缺失的难题 - 能够根据某些键解决IModalDialog的具体实现.

Roboblob在他的文章中使用ServiceLocator模式解决了最后一块拼图:

public class Bootstrapper
{
public static void InitializeIoc()
  {
    SimpleServiceLocator.SetServiceLocatorProvider(new UnityServiceLocator());
    SimpleServiceLocator.Instance.Register<IModalDialogService, ModalDialogService>();
    SimpleServiceLocator.Instance.Register<IMessageBoxService, MessageBoxService>();
  ...    
    SimpleServiceLocator.Instance.Register<IModalWindow, EditUserModalDialogView>(Constants.EditUserModalDialog);
  }
Run Code Online (Sandbox Code Playgroud)

}

所以他在他的MainWindowViewModel里面只调用静态类Get并根据一个键解析IModalDialog窗口的具体实现.

即使约什史密斯在他的文章中使用了类似的方法,但在评论中他说(DI - 构造函数注入)是一个有效的选择.

引用的StackOverflow答案还描述了可以修改和使用的类似WindowViewLoaderService.


所以问题是 - 用依赖注入替换ServiceLocator(解决IModalDialog的具体实现)的最佳方法是什么?

我的思路是:

  1. 一种可能性是(由于项目不是很大/仅由我开发)只是创建一个新服务(例如,称为IModalDialogResolver),它将创建并返回IModalDialog具体实现的新实例.手动注入所有服务.

  2. 然后我想到了一个IOC容器(Unity).我没有经验.我想也许我不必编写IModalDialogResolver,因为我可以使用Unity容器注册IModalDialog的不同实现=>但是我如何在MainWindowViewModel中使用容器?我无法将引用传递给构造函数,因为这将是ServiceLocation的一个步骤.
    所以我想也许我可以在引导程序中使用一个统一容器来解析所有服务,并在IModalDialogResolver内部使用另一个服务.但是我不知道这对于推荐使用Unity是否是一个好主意.我真的知道判断这个太少了.但有些东西告诉我,这不是一个好主意,因为它会在容器上创建一个隐藏的依赖关系+如果容器是一个单独的,只相当于将引用传递给构造函数.

为了更好地解释我所拥有的心理块:我想使用IOC容器(例如Unity)来构建和注入接口.但是我不能把IModalDialog作为参数放在构造函数中.所以我可能真的需要将它包装在一个服务中并实现自己 - 但是然后(假设Unity可以开箱即用),如果我不能使用它,那么首先将Unity放在那里是没有意义的.

我知道其中一个选择是将这个服务放入基类中,但为了争论,我们不要考虑这个.我真的想了解使用依赖注入解决这个问题的正确方法.

dev*_*tal 4

它完全有效,并且期望您能够访问组合 root中的 IoC 容器。

事实上,这应该是访问容器的唯一位置。

在您给出的示例中,这就是所发生的一切 - 具体实现正在组合根内的容器中注册。

因此,要回答您的问题,您不需要在这里替换服务定位器模式的使用,因为它只是一种在组合根中注册类型的机制,这是完全有效的。

如果您希望基于某些运行时条件实例化模式对话框服务,那么您应该注入一个模型对话框服务工厂(同样是一个在容器中注册了实现的抽象),然后该工厂将有一个方法来创建模型对话框服务和该工厂方法将获取所需的运行时参数。

然后,您的工厂可以根据运行时参数适当地新建适当的模型对话服务。或者,它还可以从容器解析适当的模型对话服务,这显然需要工厂拥有对容器的引用。

大多数容器都支持自动化工厂类型,因此您只需为工厂定义接口,容器就会使用约定自动实现工厂。例如,Castle.Windsor 具有类型化工厂设施,Unity 也有一些等效设施。