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 NestedViewModel
s from the ParentViewModel
's list, move them within the list and (importantly) between the lists of different ParentViewModel
s and these changes must be reflected in the model so there needs to be some mechanism for matching NestedViewModel
s and NestedModel
s.
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?