MVVM: How and should I expose view models’ models to other view models?

  softwareengineering

Many times while writing MVVM apps in C# I’ve come across this sort of problem where I need to expose the model in a view model so that I can get it in another view model and do something with it.

Currently I have a model with a mutable list of nested models. The models are just data + serialization logic.

public interface IParentModel
{
    public string Foo { get; set; }
    public bool Bar { get; set; }
    public IList<NestedModel> NestedModels { get; set; }
}
public interface INestedModel
{
    public int FooBar { get; set; }
    public DateTime Baz { get; set; }
}

public class ParentModel : IParentModel
{
    public string Foo { get; set; }

    public bool Bar { get; set; }

    public IList<NestedModel> NestedModels { get; set; }
    
    // ...
}

public class NestedModel : INestedModel
{
    public int FooBar { get; set; }

    public DateTime Baz { get; set; }

    // ...
}

The properties of both parent and nested models need to be edited in a property window with support for undo/redo. So I’ve added a view model class for each corresponding model class with the same properties that return the values of the model properties and push commands that mutate the model properties the onto the undo stack when changed.

public interface IParentViewModel : INotifyPropertyChanged 
{
    public string Foo { get; set; }
    public bool Bar { get; set; }
    public IList<INestedViewModel> NestedViewModels { get; }
}

public interface INestedViewModel : INotifyPropertyChanged 
{
    public int FooBar { get; set; }
    public DateTime Baz { get; set; }
}

public class ParentViewModel : ObservableObject
{
    public string Foo
    {
        get => model.Foo;
        set => SetModelMemberUndoable(nameof(model.Foo), value);
    }

    public bool Bar
    {
        get => model.Bar;
        set => SetModelMemberUndoable(nameof(model.Bar), value);
    }
    
    public IList<NestedViewModel> NestedViewModels => nestedViewModels;
    private UndoableList<NestedViewModel> nestedViewModels;

    private IParentModel model;

    // ...
}

public class NestedViewModel : ObservableObject
{
    public int FooBar
    {
        get => model.FooBar;
        set => SetModelMemberUndoable(nameof(model.FooBar), value);
    }

    public DateTime Baz
    {
        get => model.Baz;
        set => SetModelMemberUndoable(nameof(model.Baz), value);
    }

    private INestedModel model;

    // ...
}

As you can see, everything is done through interfaces so that there can potentially be different implementations of the (view) models.

The problem is that I need to be able to add and remove NestedViewModels from the ParentViewModel‘s list, move them within the list and (importantly) between the lists of different ParentViewModels and these changes must be reflected in the model so there needs to be some mechanism for matching NestedViewModels and NestedModels.


The easiest way to do this is to expose NestedModel in NestedViewModel as a public property:

public interface INestedViewModel : INotifyPropertyChanged 
{
    public int FooBar { get; set; }
    public DateTime Baz { get; set; }
    public INestedModel Model { get; }
}

public class NestedViewModel : ObservableObject
{
    public int FooBar
    // ...

    public DateTime Baz
    // ...

    public INestedModel Model => model;
    private INestedModel model;

    // ...
}

But this doesn’t feel right because I’m leaking implementation details of the view model so the view can bypass the undo stack and modify the model’s properties directly.

I can’t make this property internal because there may be other implementations in different assemblies.


Another option would be to add an IInternalParentViewModel and an IInternalNestedViewModel. The latter would expose the model, the former would have a mutable list of IInternalNestedViewModel and the list in IParentViewModel would become read only:

public interface IParentViewModel : INotifyPropertyChanged 
{
    public string Foo { get; set; }
    public bool Bar { get; set; }
    public IReadOnlyList<INestedViewModel> NestedViewModels { get; }
}

public interface INestedViewModel : INotifyPropertyChanged 
{
    public int FooBar { get; set; }
    public DateTime Baz { get; set; }
}
public interface IInternalParentViewModel : IParentViewModel 
{
    public new IList<IInternalNestedViewModel> NestedViewModels { get; }
}

public interface IInternalNestedViewModel : INestedViewModel 
{
    public INestedModel Model { get; }
}

But this also doesn’t feel right because you need one interface to mutate the properties of the parent view model and another interface to mutate the list of nested view models, even though these are going to be responsibilities of different bits of code. Also, these additional interfaces have no real semantic meaning.


How do people usually implement this when writing their apps. Is there a correct way? Is it fine to just make the model a public property? Or maybe it’s an XY problem and I need to rethink my design?

It looks like you have a fairly complicated problem with the undo and redo. However there are a few things that stand out to me in your post as “code smells”

  1. “I’ve added a view model class for each corresponding model class with the same properties..”

  2. “..everything is done through interfaces so that there can potentially be different implementations of the (view) models..”

To me these things sound like overcomplication and duplication of effort. Are you over thinking this problem?

Why not use a simple approach.

Have a MainViewModel which contains a List of Models with all their various sub objects. Don’t bother with interfaces for these POCO/struct style Models or ViewModels.

To implement the undo/redo logic we need some object that holds collection of NestedViewModel with an int of which one is current. Let call it

UnDoableList<T>
{
   List<T> items
   int i currentItemIndex
}

When we make a change, copy the NestedModel, change it, append it to the list and increment the currentItemIndex, undo, decrements the current item index etc etc

Now we can add a Dictionary of these UnDoableList with the key being the id of the NestedObject to the MainViewModel.

Now the EditNestedModelViews rather than having their own viewmodels can just be bound to the first item in the related UnDoableList, with their Edit, Undo, Redo buttons bound to functions in the MainViewModel which perform the correct logic.

Similarly with moving the nested viewmodels between ParentModels, this can be done in the MainView model, while keeping the UnDoable DIctionary so you can still undo and redo changes.

Now I’m sure you have some extra wrinkles in your real life problem, but you can see where I’m going. By Keeping all the ViewModel logic in a single main view model and binding the View and all Nested Views to properties in that MainViewModel solving the problem isn’t particularly hard.

Does this solution follow all the things that you are supposed to worry about in OOP? Maybe not. But I would argue that Views and ViewModels have a one to one relationship 99% of the time.

If you have some other page “EditModelWithNoUndo” or “ViewChangesMadeWhileEditingModel” it just best to make a new ViewModel for each. If you try to make a ViewModel that works with multiple views, or ViewModel interfaces with different implementations You are making your life super hard for no reason.

Save the cleverness for your Domain logic and keep your Presentation layer “WET”, flexible to random changes and the needs of various devices/browsers.

4

I’ve come up with an interesting solution where I introduce a new interface called IViewModelWithModel<TModel> which looks like this:

public interface IViewModelWithModel<TModel>
{
    protected TModel Model { get; }

    protected static TModel GetModel(IViewModelWithModel<TModel> viewModel)
    {
        return viewModel.Model;
    }
}

protected interface members are accessible within the same interface and child interfaces but not in implementing classes. protected static interface members, on the other hand, are accessible inside implementing classes.

This allows me to inherit IParentViewModel and INestedViewModel from this interface. This way it would be possible to access an INestedViewModel‘s model inside an IParentViewModel or its list (if it also implements IViewModelWithModel<TModel>) like so:

var model = IViewModelWithModel<INestedModel>.GetModel(nestedViewModel);

LEAVE A COMMENT