将DI-container的使用保留在Silverlight和MVVM中的组合根目录中 [英] Keeping the DI-container usage in the composition root in Silverlight and MVVM

查看:279
本文介绍了将DI-container的使用保留在Silverlight和MVVM中的组合根目录中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不太清楚我如何设计,所以我保留对Silverlight + MVVM应用程序的组合根目录中的DI容器的引用。



I有以下简单的使用场景:有一个主视图(可能是一个项目列表)和一个操作来打开一个单个项目的编辑视图。因此,主要视图必须在用户采取操作(例如点击某个按钮)时创建和显示编辑视图。



为此,我有以下代码:

  public interface IView 
{
IViewModel ViewModel {get; set;}
}

然后,对于我需要创建的每个视图我有一个抽象的工厂,像这样

  public interface ISomeViewFactory 
{
IView CreateView();
}

然后,将该工厂声明为父视图模型的依赖关系,如所以:

  public class SomeParentViewModel 
{
public SomeParentViewModel(ISomeViewFactory viewFactory)
{
//存储它
}

private void OnSomeUserAction()
{
IView view = viewFactory.CreateView();
dialogService.ShowDialog(view);
}
}

所有这一切都很好,直到这里,没有DI容器在视线:)现在来实现ISomeViewFactory:

  public class SomeViewFactory:ISomeViewFactory 
{
public IView CreateView()
{
IView view = new SomeView();
view.ViewModel = ????
}
}

????部分是我的问题,因为视图的视图模型需要从DI容器中解决,以便注入它的依赖项。我不知道的是,如果没有依赖除了组合根的任何地方,我可以做到这一点。



一个可能的解决方案是对于注入工厂的视图模型的依赖,如下所示:

  public class SomeViewFactory:ISomeViewFactory 
{
public SomeViewFactory(ISomeViewModel viewModel)
{
//存储
}

public IView CreateView()
{
IView view = new SomeView();
view.ViewModel = viewModel;
}
}

虽然这样工作,它有问题,整个对象图线被静态连接(即父视图模型将获得一个SomeViewFactory的实例,该实例将获取一个SomeViewModel的实例,只要父视图模型存在,它们就会生存),注入视图模型实现是有状态的,如果用户打开子视图两次,则第二次视图模型将是相同的实例,并且具有之前的状态。我想我可以使用初始化方法或类似的东西来解决这个问题,但是没有相当的正确的味道。



另一个解决方案可能是将DI-容器,工厂依赖包装,但它仍然是伪装的DI容器:)



对此的任何想法都非常感激。此外,请原谅任何错误或破产,因为这是我第一次在stackoverflow发帖:)



谢谢!



ps:我目前的解决方案是工厂了解DI容器,只有他们和组合根有这种依赖关系。

解决方案

为了保持尽可能接近您的示例代码,您可以以IViewPopulator的形式引入另一个间接级别:

  public interface IViewPopulator 
{
void填充(IView视图);
}

您现在可以实现您的SomeViewFactory:

  public class SomeViewFactory:ISomeViewFactory 
{
private readonly IViewPopulator populator;

public SomeViewFactory(IViewPopulator populator)
{
if(populator == null)
{
throw new ArgumentNullException(populator);
}

this.populator = populator;
}

public IView CreateView()
{
IView view = new SomeView();
this.populator.Populate(view);
返回视图;
}
}

这将创建Views和ViewModels的数量,遵守单一责任原则。在一定程度上,它也是服务汇总的示例。



您现在可以实现IViewPopulator作为具有正常依赖关系的具体类型:

  public class SomeViewPopulator:IViewPopulator 
{
private readonly IDependency dep;

public SomeViewPopulator(IDependency dep)
{
if(dep == null)
{
throw new ArgumentNullException(dep);
}

this.dep = dep;
}

public void填充(IView视图)
{
var vm = //也许使用this.dep创建一个IViewModel ... $ b的实例$ b view.ViewModel = vm;
}
}

可能还有其他方法可以建模IView和IViewModel之间的关系,但上述代表了一种可能的解决方案。



关键是不断提取抽象,直到每个人都有明确的责任。这个练习实际上并不是使得代码与容器无关,而是最终关于坚持SOLID原则。


It's not quite clear to me how I can design so I keep the reference to the DI-container in the composition root for a Silverlight + MVVM application.

I have the following simple usage scenario: there's a main view (perhaps a list of items) and an action to open an edit view for one single item. So the main view has to create and show the edit view when the user takes the action (e.g. clicks some button).

For this I have the following code:

public interface IView
{
   IViewModel ViewModel {get; set;}
}

Then, for each view that I need to be able to create I have an abstract factory, like so

public interface ISomeViewFactory
{
   IView CreateView();
}

This factory is then declared a dependency of the "parent" view model, like so:

public class SomeParentViewModel
{
   public SomeParentViewModel(ISomeViewFactory viewFactory)
   {
       // store it
   }

   private void OnSomeUserAction()
   {
      IView view = viewFactory.CreateView();
      dialogService.ShowDialog(view);
   }       
} 

So all is well until here, no DI-container in sight :). Now comes the implementation of ISomeViewFactory:

public class SomeViewFactory : ISomeViewFactory
{
    public IView CreateView()
    {
        IView view = new SomeView();
        view.ViewModel = ????   
    }
}

The "????" part is my problem, because the view model for the view needs to be resolved from the DI-container so it gets its dependencies injected. What I don't know is how I can do this without having a dependency to the DI-container anywhere except the composition root.

One possible solution would be to have either a dependency on the view model that gets injected into the factory, like so:

public class SomeViewFactory : ISomeViewFactory
{
    public SomeViewFactory(ISomeViewModel viewModel)
    { 
       // store it
    }

    public IView CreateView()
    {
        IView view = new SomeView();
        view.ViewModel = viewModel;
    }
}

While this works, it has the problem that since the whole object graph is wired up "statically" (i.e. the "parent" view model will get an instance of SomeViewFactory, which will get an instance of SomeViewModel, and these will live as long as the "parent" view model lives), the injected view model implementation is stateful and if the user opens the child view twice, the second time the view model will be the same instance and have the state from before. I guess I could work around this with an "Initialize" method or something similar, but it doesn't smell quite right.

Another solution might be to wrap the DI-container and have the factories depend on the wrapper, but it'd still be a DI-container "in disguise" there :)

Any thoughts on this are greatly appreciated. Also, please forgive any mistakes or rule-breaking, since this is my first post on stackoverflow :)

Thanks!

ps: my current solution is that the factories know about the DI-container, and it's only them and the composition root that have this dependency.

解决方案

To stay as close to your example code as possible, you can introduce yet another level of indirection in the form of an IViewPopulator:

public interface IViewPopulator
{
    void Populate(IView view);
}

You can now implement your SomeViewFactory like this:

public class SomeViewFactory : ISomeViewFactory
{
    private readonly IViewPopulator populator;

    public SomeViewFactory(IViewPopulator populator)
    {
        if (populator == null)
        {
            throw new ArgumentNullException("populator");
        }

        this.populator = populator;
    }

    public IView CreateView()
    {
        IView view = new SomeView();
        this.populator.Populate(view);
        return view;
    }
}

This separates the creation of Views and the population of ViewModels, adhering to the Single Responsibility Principle. To a certain extent, it is also an example of Service Aggregation.

You can now implement IViewPopulator as a concrete type that takes normal dependencies:

public class SomeViewPopulator : IViewPopulator
{
    private readonly IDependency dep;

    public SomeViewPopulator(IDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    public void Populate(IView view)
    {
        var vm = // Perhaps use this.dep to create an instance of IViewModel...
        view.ViewModel = vm;
    }
}

There are likely to be other ways you could model the relationship between IView and IViewModel, but the above represents one possible solution.

The key is to keep extracting abstractions until each have a well-defined responsibility. This exercise is really not about making code Container-agnostic at all, but eventually about adhering to SOLID principles.

这篇关于将DI-container的使用保留在Silverlight和MVVM中的组合根目录中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆