MVVM IoC 挑战 : implement concrete class for this ViewModel factory interface

标签 mvvm dependency-injection inversion-of-control unity-container

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

我正在尝试创建一个可以注入(inject)到我的 ViewModel 中的 ViewModel 工厂服务。该工厂的接口(interface)可能如下所示:

public interface IViewModelFactoryService {
    TViewModel Create<TViewModel, TModel>(TModel domainObject) 
        where TViewModel : ViewModelBase 
        where TModel : DomainObject;
}

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

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

以下是该工厂需要创建的一些 ViewModel 构造函数的外观:
FooViewModel(Foo model)
BarViewModel(Bar model, IViewModelFactoryService factory)
BazViewModel(Baz model, IViewModelFactoryService factory, IRepository repository)

以下是使用 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>()))))

一旦这些工厂在容器中注册,就可以使用以下代码:
barFactory = container.Resolve<Func<Bar, BarViewModel>>();
barViewModel = barFactory(myBar);

bazFactory = container.Resolve<Func<Baz, BazViewModel>>();
bazViewModel = bazFactory(myBaz);

当然,我的最终目标是做到以下几点:
viewModelFactory = container.Resolve<IViewModelFactory>();
barViewModel = viewModelFactory.Create<BarViewModel, Bar>(myBar);
bazViewModel = viewModelFactory.Create<BazViewModel, Baz>(myBaz);

如果我能做到这一点,我就可以在任何我想要的地方注入(inject) IViewModelFactoryService,并确保我可以为任何类型创建一个 View 模型,只要我可以访问被包装的模型对象。

我认为必须有某种方法可以使用各个 ViewModel 的工厂来创建我描述的 IViewModelFactoryService 的具体实现,但是我无法以正确的方式将这些部分组合在一起。

我可以为每个 ViewModel 的每个工厂为具体的 IViewModelFactoryService 类创建一个构造函数参数,但这显然是不可取的。我可以在具体的 IViewModelFactoryService 类上对属性注入(inject)做类似的事情,但是我最终会为每个 ViewModel 定义单独的属性。这两种可能的途径都是不可取的,因为我每次创建新的 ViewModel 时都必须修改具体的 IViewModelFactoryService 类。

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

仅供引用,在我弄清楚(如果我这样做)之前,我已经下注并正在容器外创建我的 ViewModel。基本上我在做“穷人的依赖注入(inject)”。需要创建 ViewModel 的 ViewModel 被注入(inject)了新 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
    }

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

更新

这里有几篇博文让我倾向于将容器排除在 IViewModelFactoryService 具体类之外。结果是注册发布解析模式和对组合根的强调相结合,以保持代码库干净并避免业务逻辑中对 IoC 容器的“隐藏”依赖。
  • Register Resolve Release pattern
  • Using the Inversion of Control container
  • Pulling from the container after initial composition

  • 当然,如果将容器注入(inject)到具体的工厂类中可以让整个问题消失,让我不再头疼,那么牺牲一点纯度也许是合理的。但感觉就像放弃寻找优雅的解决方案。

    最佳答案

    根据 Patrice 的评论,我继续创建了一个注入(inject)容器的具体类的版本。以这种方式创建当然很容易,我可以相信它可以被视为 Composition Root 的一部分。

    这是接口(interface)的最终版本(支持包装 DomainObject 的 ViewModel 和不包装的 ViewModel)。

    public interface IViewModelFactory {
        TViewModel Create<TViewModel, TModel>(TModel domainObject) where TViewModel : ViewModelBase where TModel : DomainObject;
        TViewModel Create<TViewModel>() where TViewModel : ViewModelBase;
    }
    

    这是 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;
        }
    }
    

    我还需要为我希望能够使用工厂创建的每个 ViewModel 以及 ViewModelFactory 本身注册一个工厂委托(delegate)。在此示例中,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));
    

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

    关于MVVM IoC 挑战 : implement concrete class for this ViewModel factory interface,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30517996/

    相关文章:

    spring - 有没有办法覆盖组件扫描发现的 bean?

    multithreading - WCF 中的 UnitOfWork 与 Quartz(线程)

    .net - 如何用 IoC 容器组织 MVP?

    wpf - 每个模块的菜单项,模块内容使用 Prism 或 MEF 动态加载

    c# - 根据角色限制用户可以做什么

    javascript - 如何在模块导入/配置中设置 .env 变量

    asp.net-mvc - PerRequestLifetimeManager 和 Task.Factory.StartNew - 使用 Unity 进行依赖注入(inject)

    c# - 在 MVVM 中序列化有很多问题

    c# - MVVM C#​​ 与远程计算机上的 View

    c# - System.web.Mvc DependencyResolver 无法为泛型类创建对象