0
votes

Short version:

If I have ViewModel, containing its Model object and exposing its properties, how do I get the model "back" after it has been edited? If the Model-inside-ViewModel is public, it violates encapsulation, and if it is private, I cannot get it (right?).


Longer version:

I am implementing a part of an application which displays collections of objects. Let's say the objects are of type Gizmo, which is declared in the Model layer, and simply holds properties and handle its own serialization/deserialization.

In the Model layer, I have a Repository<T> class, which I use to handle collections of MasterGizmo and DetailGizmo. One of the properties of this repository class is an IEnumerable<T> Items { get; } where T will be some of the Gizmo subtype.

Now since Gizmo doesn't implement INPC, I have created the following classes in ViewModel layer:

  • GizmoViewModel, which wraps every public property of a Gizmo so that setting any property raises PropertyChanged accordingly;

  • [**] RepositoryViewModel<T>, which has an ObservableCollection<GizmoViewModel> whose CollectionChanged is listened to by a method that handles Adds, Removes and Updates to the repository.

Notice that the Model layer has a "Repository of Models", while the ViewModel layer has a "ViewModel with an ObservableCollection of ViewModels".

The doubt is related to the [**] part above. My RepositoryViewModel.CollectionChangedHandler method is as follows:

    void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (var added in e.NewItems)
                {
                    var gvm = added as GizmoViewModel;
                    if (gvm != null)
                    {
                         //// IS ANY OF THE ALTERNATIVES BELOW THE RIGHT ONE?
                         // Gizmo g = gvm.RetrieveModel();          ?? proper getter ??
                         // Gizmo g = GetModelFromViewModel(gvm);   ?? external getter ??
                         // Gizmo g = gvm.Model;                    ?? public model property ??

                        _gizmo_repository.Add(g);
                    }
                }
                break;
            ....

Besides that, if anyone can detect any MVVM smell here, I'll be happy to know.

4

4 Answers

1
votes

Reading your code, I think there is something of a mixup regarding your ViewModel and Model separation.

So, as I understand it, when your ObservableCollection of GizmoViewModel's changes, you are trying to add the Gizmo instance of the new item back to your Model?

I would approach this differently. You should create your Gizmo instances inside your Model layer, and when you do this you should add it to the Repository.

Otherwise, you haven't provided enough information - or rather, you have provided too much but it is the wrong sort of information. You need to describe the situation in which you want to do this, where these GizmoViewModels are created, etc.

1
votes

We can deal with our Models even outside the View and ViewModel layers, so leaving the model publicly accessible from ViewModel is I believe acceptable.

Let say you are creating the Models in "DataLayer" you can pass the instance of the Model to the ViewModel. To illustrate my point:

///Models ////////////////////////////
public interface IGizmo{}
public class Gizmo:IGizmo{}
public class SuperGizmo : IGizmo {}
public class SuperDuperGizmo : IGizmo { }
//////////////////////////////////////

public interface IGizmoViewModel<out T>
{
    T GetModel();
}

public abstract class GizmoViewModelBase : IGizmoViewModel<IGizmo>
{
    protected GizmoViewModelBase(IGizmo model)
    {
        _Model = model;
    }

    private readonly IGizmo _Model;
    public IGizmo GetModel()
    {
        return _Model;
    }
}

public class GizmoViewModel : GizmoViewModelBase
{
    public GizmoViewModel(Gizmo model)
        : base(model) { }
}

public class SuperDuperGizmoViewModel : GizmoViewModelBase
{
    public SuperDuperGizmoViewModel(SuperDuperGizmo model)
        : base(model){}
}

Your repository of Models will be updated on whatever updates it get from the ViewModel as long as you passed the same instance. So there is no need to have a repository of ViewModels to get the updates.

0
votes

From what I can see here, your GizmoViewModel has a dependency to your Repository<T>, so why not pass in the repository when you create your view model?

public class GizmoViewModel
{
    private IRepository<Gizmo> _Repo;

    //Underlying model (Doesn't implement INotifyPropertyChanged)
    private Gizmo _Model;

    //Wrapping properties
    public int MyProperty
    {
        get { return _Model.Property; }
        set
        {
            _Model.Property = value;
            NotifyOfPropertyChange();
        }
    }

    ...

    public GizmoViewModel(IRepository<Gizmo> repo)
    {
        _Repo = repo;
    }

    public void AddToRepo()
    {
        _Repo.Add(_Model);
    }

    ...

It would be even better if these methods are inside the RepositoryViewModel base class. You can really go crazy with inheritance here. Perhaps something like this:

var gvm = added as IRepositoryViewModel;

if (gvm != null)
    gvm.AddToRepo();

You can then simply call AddToRepo when you need to add the view model's underlying model to the repository.

Perhaps not the most elegant solution, however if encapsulation is what's worrying you, then you need to ensure that your dependencies are properly managed.

0
votes

"If the Model-inside-ViewModel is public, it violates encapsulation"

Your assertion above is completely wrong and is killing your code.
By setting the Model property in ViewModel as private, you are forced to repeat your self ( code smells ), as you will need to define in your ViewModel, the same properties as you did for your Model, effectively transforming it into a Model class that mimics the Model it is supposed to expose to the View.

In MVVM the ViewModel role is to provide the View with all the presentation data and logic that it needs and for sure the Model is fundamental part of this data, by hidding it from the View you are killing MVVM.