Resolve ViewModels for collection of (differently typed) Models

Topics: Prism v4 - WPF 4
Jan 20, 2012 at 9:18 PM
Edited Jan 20, 2012 at 9:19 PM

Hi experts,

Suppose we have a list of different models who only share the same base type

class Model1 : ModelBase {}
class Model2 : ModelBase {}
class Model3 : ModelBase {}

var myList = new List<ModelBase> { new Model1(), new Model2(), new Model3()}

How do I resolve/create a ViewModel for each of this Models based on it's type using PRISM/MEF? At first I was thinking about using something like this (pseudocode)

var myVMList;

foreach (model in myList)
{
   switch(model)
       case Model1
          myVMList.Add(new Model1ViewModel(model)

       case Model2
          myVMList.Add(new Model2ViewModel(model))
}

But this has the drawback that 'switch-on-type' is not supported in C#. There are some implementations by others but mostly it's suggested to just go with polymofism (create virtual GetViewModel method in ModelBase and just override this in direved classes.) This is in my case not ideal because it couples my Model with the ViewModel which should never be the case.

I also found some answers to 'switch-on-type' discussions who suggest using IoC for this (like MEF) but i can't figure out how to do this. There is no method to get a viewmodel based on the models name as far as i'm concerned in PRISM. The only posibility would be to use Import attribute, which is useless here or go with ServiceLocator.Current.GetInstance<Type>(); but that requires an actual type rather than a string.

Hope you can help me with this!

Many thanks!

Developer
Jan 23, 2012 at 6:46 PM

Hi,

Based on my understanding, the usual approach when using the MVVM pattern in Prism is to create the view, obtain the corresponding view model through dependency injection and then obtain the corresponding models in the view model also through dependency injection or through a shared service. This way, the model doesn't need to know the view model that is going to consume it.

However, this depends mostly of your personal preferences and the requirements of your scenario.

If you have an scenario where you need to instantiate your model first (for example, to perform some kind of initialization on it) and then create the view model that will consume it passing the aforementioned model in the constructor, you could use the approach you mentioned above. For example, I believe you could use the type's name in the switch statement:

foreach (ModelBase model in myList)
{
    String type=model.GetType().Name;    
    switch(type)
    {
        case "Model1":
            myVMList.Add(new Model1ViewModel(model));
            break;

        case "Model2":
            myVMList.Add(new Model2ViewModel(model));
            break;

        ( ... )
    }
}

However, take into account that, based on my understanding, this might not be a recommended approach as it might be possible for a model to be consumed by more than one view model or to have a view model that consumes more than one model.

Also, another possible approach could be to implement a kind of "presenter" for each view - view model - model triad that could build the aforementioned components and wire them up.

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini

Jan 23, 2012 at 8:35 PM
Edited Jan 23, 2012 at 9:19 PM

Thank you very much for your answer. Yeah I was in the understanding that it is recommended to create a View first, then let DI create a ViewModel. I saw that in multiple sources, but I am confused on how to handle this in situations where you already have a collection of (different types) of Models.

For instance: Suppose you have a service which generates random Models, based on some algorithm. After a minute or so the service has created 100 Models, lets say 50 of type Square and 50 of type Circle (both Models share the same base class ModelBase). Suppose each Model has a Color property and a Location property (X,Y). Now I want to display these Models in a Canvas (not a problem), but I want it to be done the MVVM way.

When I understand MVVM correctly I now need to create a View for each Model (in real world models are somewhat more complex than described here, so a View for each model is not overdesigned) and a ViewModel linking the View to the Model without them knowing of eachother. With PRISM I now should do als you told: Create a View first, which loads a ViewModel via DI; this is a problem because I need to create a Views and VMs that belong to the already existent Models, so the Circle models should get a Circle view and a CircleViewModel and the same holds for the Square Models.

Of course I could create a method CreateVM() or even CreateView() in the Models but that would break with all rules of MVVM and composition. The service who creates these models should not know anything about the VMs and Views. I'm very curious as to what your idea around this kind of problem is.

Thank you very much for your time.

Jan 24, 2012 at 12:25 AM
Edited Jan 24, 2012 at 12:52 PM

Okay to make things more clear to you I have created some code, hope this will help you better understand my problem. Thanks again!

    /*
     * Models (these are generated by a service, the service doesn't and should not now about views or view models)
     * 
     * 
     */

    abstract class BaseModel { }

    class SquareModel : BaseModel { }

    class CircleModel : BaseModel { }


    /*
     *  View Models
     * 
     * 
     */

    abstract class BaseViewModel<TModel> // : INOtificationPropertyChanged, etc
    {
        protected TModel Model;

        public void SetModel(TModel model)
        {
            Model = model;
            OnChangeModel();
        }

        protected virtual void OnChangeModel()
        {
            // Assignment of base properties here, based on Model
        }

        // Declarate some base properties here
    }

    [Export(typeof(BaseViewModel<BaseModel>))]
    [TypeMetadata(Type = "CircleViewModel")]
    class CircleViewModel : BaseViewModel<CircleModel>
    {
        protected override void OnChangeModel()
        {
            // Assignment of circle specific properties here, based on Model
        }

        // Declarate some circle specific properties here
    }

    [Export(typeof(BaseViewModel<BaseModel>))]
    [TypeMetadata(Type = "SquareViewModel")]
    class SquareViewModel : BaseViewModel<SquareModel>
    {
        protected override void OnChangeModel()
        {
            // Assignment of square specific properties here, based on Model
        }

        // Declarate some square specific properties here
    }

    class Program
    {
        [ImportMany]
        protected IEnumerable<ExportFactory<BaseViewModel<BaseModel>, ITypeMetadata>> Factories { get; set; }

        public BaseViewModel<BaseModel> Create(string viewModelType)
        {
            var factory = (from f in Factories where f.Metadata.Type.Equals(viewModelType) select f).First();

            // Factory is able to create View Models of type viewModelType using CreateExport() function
            var vm = factory.CreateExport().Value;

            return vm;
            // Same error as with solution A
            // cannot convert from 'ConsoleApplication1.SquareViewModel' to 'ConsoleApplication1.BaseViewModel<ConsoleApplication1.BaseModel>'
            // This error is actually displayed in ExportFactory context, but it means the same
        }

        public BaseViewModel<BaseModel> CreateFrom(Type type)
        {
            var vmTypeName = type.Name + "ViewModel";
            return Create(vmTypeName);
        }

        public BaseViewModel<BaseModel> CreateVMUsingExportFactory(BaseModel model)
        {
            var vm = CreateFrom(model.GetType());
            vm.SetModel(model);
            return vm;
        }

        public void DoStuff()
        {
            // Suppose service gives me this
            var serviceOutput = new List<BaseModel>
                                    {
                                        new SquareModel(),
                                        new CircleModel(),
                                        new CircleModel(),
                                        new SquareModel(),
                                        new CircleModel(),
                                        new SquareModel(),
                                        new SquareModel()
                                        // may be longer but not the point
                                    };

            // viewModelCollection is bound to a listbox, by using datatemplates everthing is nicely placed on the canvas; no problem there
            // Actually this is a ObserveableCollection
            List<BaseViewModel<BaseModel>> viewModelCollection = new List<BaseViewModel<BaseModel>>();


            //
            // What to do here?
            //

            //
            // A. Switch-on-type
            foreach (var model in serviceOutput)
            {
                // Note there are beter implementations of this, using dicationaries and delegates, main goal of that is to not break when refactoring;
                switch (model.GetType().Name)
                {
                    case "SquareModel":
                        SquareViewModel vm = new SquareViewModel();
                        vm.SetModel((SquareModel)model); // another cast..... :(
                        viewModelCollection.Add(vm);
                        // Error: 
                        // cannot convert from 'ConsoleApplication1.SquareViewModel' to 'ConsoleApplication1.BaseViewModel<ConsoleApplication1.BaseModel>'

                        break;

                    case "CircleModel":
                        // same
                        break;
                }
            }

            // B. MEF ExportFactory<>
            //
            foreach (var model in serviceOutput)
            {
                var vm = CreateVMUsingExportFactory(model);
                viewModelCollection.Add(vm);
            }

            // C. Something else?!
            //
            // Please help ;-).
        }

If anything is unclear please ask :)

Developer
Jan 24, 2012 at 6:04 PM

Hi,

If you need to create a view and view model and then obtain an already existing model for them, as a possible approach you could use dependency injection to create the view and view model and inject in the view model the service used to obtain the existing models.

If instead, you need to create a view and/or view model for each existing model, I believe you could use a similar approach to the one you mentioned above: you could have a shared service (this would be a different service than the one used to obtain the models) that contains a dictionary of model types and delegates or "presenters" (for example, a method or object which knows how to create the view or view model for the specific model type and how to wire them together). When you need to create a view model for a specific model, you could pass this model to the service which could, for example, return the corresponding view model with the model inside it. The dictionary of the aforementioned service could be populated, for example, when a module is initialized: the module could register in the shared service the model types that are defined inside it, and the delegates or "presenters" used to create the corresponding view models.

Also, as a possible approach to avoid the casting exceptions that you are mentioning, you could create an interface (for example, IBaseViewModel) that could be implemented by the BaseViewModel class. Then, the viewModelCollection (or any collection you want to use to save the view models) could be of type IBaseViewModel instead of type BaseViewModel. Based on my understanding, as the SquareViewModel and the CircleViewModel would also implement IBaseViewModel through inheritance, the exception might be avoided.

I hope you find this useful,

Damian Cherubini
http://blogs.southworks.net/dcherubini

Jan 24, 2012 at 8:16 PM

Hi DCherubini,

Thanks again for your quick and helpful reply. Short before you replied I already found out about the use of an interface to be able to put everything together in a list. Seeying you come up with the exact same solution makes me confident i'm on the right path. With the introduction of the interface the ExportFactory<> also works like a charm. When I think of it, it does exactly as you described regarding the shared service!

    public interface IViewModelBase
    {
        void SetModel(object model);
        bool HasModel(object model);
    }

together with this base class:

public abstract class ViewModelBase
        : ExtendedNotificationObject, IViewModelBase where TModel : class
    {
        protected TModel Model;

        public void SetModel(TModel model)
        {
            Model = model;
            OnModelChanged();
        }

        public void SetModel(object model)
        {
            if (model is TModel)
            {
                SetModel((TModel) model);
            }
        }

        public bool HasModel(TModel model)
        {
            return Model == model;
        }

        public bool HasModel(object model)
        {
            return Model == model;
        }

        protected virtual void OnModelChanged()
        {
            // Set base properties here
        }

        // Define base properties
}

Now makes it possible to create the CircleViewModel and SquareViewModels and put them in a single List<IViewModelBase> .

The shared service you mentioned is also easy to realise once you have that interface and knowledge of ExportFactory<>:

    [Export]
    public sealed class ViewModelFactory<TIViewModelBase>
        where TIViewModelBase : IViewModelBase
    {
        //http://mef.codeplex.com/wikipage?title=Exports%20and%20Metadata
        [ImportMany(AllowRecomposition = true)]
        private IEnumerable<ExportFactory<TIViewModelBase, ITypeMetadata>> Factories { get; set; }

        public TIViewModelBase Create(string viewModelType)
        {
            // Factory creates view models of type viewModelType
            var factory = (from f in Factories
                           where f.Metadata.Type.Equals(viewModelType)
                           select f).First();

            return factory.CreateExport().Value;
        }

        public TIViewModelBase CreateFrom(Type type)
        {
            var vmTypeName = type.Name + "ViewModel";
            return Create(vmTypeName);
        }

        public TIViewModelBase CreateFrom(object model)
        {
            var vm = CreateFrom(model.GetType());
            vm.SetModel(model);
            return vm;
        }
    }

 Using it now only requires importing the factory above and calling CreateFrom(<My Model>) to receive a correctly instantiated view model based on the supplied model! The default CreationPolicy for the factory above is NonShared, does this mean that it returns the same instance for

[import]
ViewModelFactory<IBaseViewModel>Factories1

[import]
ViewModelFactory<IAnotherSetOfViewModelsThatHaveThisType> Factories2

I expect it not too (as TIViewModel differs).

Do you think this is the right approach? To me it feels a lot better than what I had before, but you are the expert ;-). Thanks again for your help :)

Developer
Jan 26, 2012 at 8:09 PM

Hi,

Based on my understanding, I believe that the approach you mentioned above could be valid to fulfill this scenario. Thanks for sharing it with the rest of the community as it might be useful for other users with similar concerns.

Thanks,

Damian Cherubini
http://blogs.southworks.net/dcherubini