Initialize hierarchical tree viewmodels with unity

2019-09-06 23:28发布

问题:

I'm investigating a change in our application to use Prism. At the moment I'm struggling with the initialization of hierarchical structures.

Basically I have a base class of which other classes inherit, simplified like this:

public class NodeViewModel : INodeViewModel
{
    Node Node { get; private set; }
    public ObservableCollection<INodeViewModel> ChildNodeViewModels { get; private set; }

    public NodeViewModel(IUnityContainer container, Node node)
    {
        Node = node;
        ChildNodeViewModels = new ObservableCollection<INodeViewModel>();

        foreach (Node childNode in Node.ChildNodes)
        {
            // Some initialization code
        }
    }
}

Up to now I didn't use the container so we only had the Node argument. Each inherited class had an attribute which we used for model to viewmodel mapping.

[ModelToViewModelMapping(typeof(InheritedNode ))]
public class InheritedViewModel: NodeViewModel
{
    public InheritedViewModel(InheritedNode inheritedNode)
        : base(inheritedNode)
    {
    }
}

I then had an initialization line like this:

ChildNodeViewModels.Add((INodeViewModel)System.Activator.CreateInstance(ModelToViewModelMappingDictionary.GetMappedViewModelType(childNode.GetType()), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, null, new object[] { childNode }, null));

My question is twofold.

  • I don't know if my old trick with the attribute is a common solution for the mapping. I suppose not but it keeps things together and manageable in an easy way. I'm not sure the viewmodellocator is a good solution and I'm completely new with it, I haven't tried.
  • How can the initialization be done. The Resolve method uses a type that I can resolve in one way or another (my attribute solution, viewmodellocator, ...) but Unity's ParameterOverride needs a string parameterName. As shown in the code above that fails because the parameterName is not constant, every inheritted class has its own parameterName.

Thanks in advance!

回答1:

Ad 1:

I like your current option because you avoid creating a lot of factories this way.

Ad 2:

You have to provide the parameter name for unity, so you have to manually reflect the constructor and find the name of the parameter you want to override. Then you can resolve your view model through unity and pass in the model. But there's a catch! When resolving with a parameter override, you override any parameters that happen to match the parameter name in the override, anywhere in the hierarchy and no matter what type. In the best case, the types don't match and you get an exception, in the worst case, a wrong dependency is injected, silently. Have fun debugging that...

Recommendation:

Stick with your current way of producing the view models, and inject the dependencies when the objects are constructed. This has drawbacks, too, because you cannot use constructor injection and you need a reference to the container, but I'd prefer those to the rather unpredictable parameter override.

var viewModel = Activator.CreateInstance(...);
_container.BuildUp( viewModel );

with

internal class MyChildViewModel
{
    [Dependency]
    public ISomeService MyDependency { get; set; }
}


回答2:

  1. Your trick is common. It is more common to see:

InheritedViewModel < InheritedNode >

Where also:

NodeViewModel < T >

And of course:

INodeViewModel < T >

  1. Part of the power of Prism is getting the Instance you need from the Container like this:

Container(or how you'll call it).GetInstance(or whatever its called) < INodeViewModel < InheritedNode >>()

You need to register first in the container InheritedViewModel < T > with INodeViewModel < T > for that to work