简体   繁体   中英

Keeping the DI-container usage in the composition root in Silverlight and MVVM

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 (eg 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" (ie 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 :)

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM