MVVM IoC挑战:为此ViewModel工厂接口实现具体类

Dav*_*ter 5 dependency-injection inversion-of-control unity-container mvvm

我正在开发一个Windows Store应用程序,我想使用MVVM,Unity和IoC.我正在努力创建包装Model对象的ViewModel.其他各种帖子都提出了类似的问题,但我认为这是一个略有不同的看法.

我正在尝试创建一个ViewModel工厂服务,我可以将其注入到我的ViewModel中.该工厂的界面可能如下所示:

public interface IViewModelFactoryService {
    TViewModel Create<TViewModel, TModel>(TModel domainObject) 
        where TViewModel : ViewModelBase 
        where TModel : DomainObject;
}
Run Code Online (Sandbox Code Playgroud)

问题是我试图将模型对象传递给ViewModel构造函数并将服务作为附加参数注入.我也试图遵循工厂不应该引用IoC容器的原则,因此在工厂内使用container.Resolve不是一种选择.

使问题复杂化的是,不同的ViewModel当然可以要求不同的服务.我相信解决方案可能涉及使用InjectionFactory(一个Unity对象,它允许您为注册类型配置工厂),但我似乎无法让所有部分都适合.

以下是此工厂需要创建的一些ViewModel构造函数的外观:

FooViewModel(Foo model)
BarViewModel(Bar model, IViewModelFactoryService factory)
BazViewModel(Baz model, IViewModelFactoryService factory, IRepository repository)
Run Code Online (Sandbox Code Playgroud)

下面是使用Unity的InjectionFactory为其中两个ViewModel类创建工厂的示例:

container.RegisterType<Func<Bar, BarViewModel>>(new InjectionFactory(
    c => new Func<Bar, BarViewModel>(
        bar => new BarViewModel(bar, 
            c.Resolve<IViewModelFactoryService>()))))

container.RegisterType<Func<Baz, BazViewModel>>(new InjectionFactory(
    c => new Func<Baz, BazViewModel>(
        baz => new BazViewModel(baz, 
            c.Resolve<IViewModelFactoryService>(), 
            c.Resolve<IRepository>()))))
Run Code Online (Sandbox Code Playgroud)

一旦这些工厂注册了容器,就可以使用以下代码:

barFactory = container.Resolve<Func<Bar, BarViewModel>>();
barViewModel = barFactory(myBar);

bazFactory = container.Resolve<Func<Baz, BazViewModel>>();
bazViewModel = bazFactory(myBaz);
Run Code Online (Sandbox Code Playgroud)

当然,我的最终目标是做到以下几点:

viewModelFactory = container.Resolve<IViewModelFactory>();
barViewModel = viewModelFactory.Create<BarViewModel, Bar>(myBar);
bazViewModel = viewModelFactory.Create<BazViewModel, Baz>(myBaz);
Run Code Online (Sandbox Code Playgroud)

如果我能够达到这一点,我可以在任何地方注入IViewModelFactoryService,并确保只要我有权访问被包装的模型对象,我就可以为任何类型创建视图模型.

我认为必须有一些方法可以使用工厂为各个ViewModel创建我所描述的IViewModelFactoryService的具体实现,但我不能以正确的方式将各个部分组合在一起.

我可以为每个ViewModel的每个工厂为具体的IViewModelFactoryService类创建一个构造函数参数,但这显然是不可取的.我可以在具体的IViewModelFactoryService类上执行类似于属性注入的操作,但最后我会为每个ViewModel定义单独的属性.这两种可能的途径都是不可取的,因为每次创建新的ViewModel时我都必须修改具体的IViewModelFactoryService类.

也许DynamicObject可能在这里有用.到目前为止,我已经在WinRT中避免了它,因为它似乎不能从XAML绑定到DynamicObject的动态属性.但这对于IViewModelFactoryService具体类来说不是问题.

仅供参考,直到我弄明白这一点(如果我这样做),我已经开始尝试并在容器外创建我的ViewModel.基本上我在做"穷人的依赖注射".需要创建ViewModel的ViewModel会注入新ViewModel可能需要的所有参数.例如,如果BarViewModel需要创建BazViewModel:

class BarViewModel {
    IRepository _repository;
    Bar _bar;
    BarViewModel(Bar bar, IRepository repository) {
        _bar = bar;
        _repository = repository;
    }

    void DoSomethingWithBaz(Baz baz) {
        var bazViewModel = new BazViewModel(baz, _repository);
        // do something with bazViewModel
    }
Run Code Online (Sandbox Code Playgroud)

当然,这样做的缺点是Bar本身不应该依赖IRepository.它只会被注入,因为它在构造BazViewModel时需要使用它.抽象工厂模型将消除Bar对IRepository的依赖性.另一个缺点是容器不再管理BazViewModel而且我必须自己进行注射.

UPDATE

这里有一些博客文章让我倾向于将容器保留在IViewModelFactoryService具体类之外.结果是Register Release Resolve模式和强调Composition Roots的组合,以保持代码库的清洁,并避免业务逻辑中IoC容器的"隐藏"依赖性.

当然,如果将容器注入混凝土工厂类会使整个问题消失并使我不再担心我的头痛,也许牺牲一点纯度是合理的.但感觉放弃寻找优雅的解决方案.

Dav*_*ter 3

根据 Patrice 的评论,我继续创建了注入容器的具体类的版本。以这种方式创建当然很容易,而且我可以接受这样的论点:它可以被视为组合根的一部分。

这是界面的最终版本(支持包装 DomainObject 和不包装 DomainObject 的 ViewModel)。

public interface IViewModelFactory {
    TViewModel Create<TViewModel, TModel>(TModel domainObject) where TViewModel : ViewModelBase where TModel : DomainObject;
    TViewModel Create<TViewModel>() where TViewModel : ViewModelBase;
}
Run Code Online (Sandbox Code Playgroud)

这是 ViewModelFactory 的实现:

public class ViewModelFactory : IViewModelFactory, IDisposable {
    IUnityContainer _container;
    public ViewModelFactory(IUnityContainer container) {
        if (null == container) throw new ArgumentNullException("container");
        _container = container;
    }

    public TViewModel Create<TViewModel, TModel>(TModel domainObject)
        where TViewModel : GalaSoft.MvvmLight.ViewModelBase
        where TModel : DomainObject {
        return _container.Resolve<Func<TModel, TViewModel>>()(domainObject);
    }


    public TViewModel Create<TViewModel>() where TViewModel : GalaSoft.MvvmLight.ViewModelBase {
        return _container.Resolve<TViewModel>();
    }

    public void Dispose() {
        _container = null;
    }
}
Run Code Online (Sandbox Code Playgroud)

我还需要为我希望能够使用工厂创建的每个 ViewModel 以及 ViewModelFactory 本身注册一个工厂委托。在此示例中,StackListItemViewModel 是包装名为 Stack 的 DomainObject 的 ViewModel:

container.RegisterType<Func<Stack, StackListItemViewModel>>(
    new InjectionFactory(c => new Func<Stack, StackListItemViewModel>(
        stack => new StackListItemViewModel(stack, container.Resolve<IRepository>()))));

container.RegisterType<IViewModelFactory, ViewModelFactory>(new ContainerControlledLifetimeManager(), new InjectionConstructor(container));
Run Code Online (Sandbox Code Playgroud)

请注意,我不想将容器本身的实例注册到容器中。如果我这样做,对于决定开始使用容器作为 ServiceLocator 的人来说,这就会变得很滑坡。这就是为什么我使用 InjectionConstructor 将容器传递给 ViewModelFactory 的构造函数。