33

I've spent quite some time to try and find an elegant solution for the following challenge. I've been unable to find a solution that's more than a hack around the problem.

I've got a simple setup of a View, ViewModel and a Model. I will keep it very simple for the sake of explanation.

  • The Model has a single property called Title of type String.
  • The Model is the DataContext for the View.
  • The View has a TextBlock thats databound to Title on the Model.
  • The ViewModel has a method called Save() that will save the Model to a Server
  • The Server can push changes made to the Model

So far so good. Now there are two adjustments I need to make in order to keep the Model in sync with a Server. The type of server is not important. Just know that I need to call Save() in order to push the Model to the Server.

Adjustment 1:

  • The Model.Title property will need to call RaisePropertyChanged() in order to translate changes made to the Model by the Server to the View. This works nicely since the Model is the DataContext for the View

Not too bad.

Adjustment 2:

  • Next step is to call Save() to save changes made from the View to the Model on the Server. This is where I get stuck. I can handle the Model.PropertyChanged event on the ViewModel that calls Save() when the Model gets changed but this makes it echo changes made by the Server.

I'm looking for an elegant and logical solution and am willing to change my architecture if it makes sense.

ndsc
  • 1,173
  • 2
  • 13
  • 22
  • 8
    Something's weird.. using Model as Datacontext? that's not actual MVVM. firstly just test the ViewModel as if "no existence of View". – aifarfa May 03 '12 at 18:34
  • The application is UI heavy and I've chosen to try an approach to translate the Model directly to the View. However, some properties get handled by the ViewModel. See this: http://stackoverflow.com/a/10324065/1120175 – ndsc May 03 '12 at 18:51
  • @ndsc Please have a deeper look at your referenced question, exspecially my answer. A Model as DataContext does drastically violate the MVVM pattern. – PVitt May 04 '12 at 12:37

5 Answers5

76

In the past I 've written an application that supports "live" editing of data objects from multiple locations: many instances of the app can edit the same object at the same time, and when someone pushes changes to the server everyone else gets notified and (in the simplest scenario) sees those changes immediately. Here's a summary of how it was designed.

Setup

  1. Views always bind to ViewModels. I know it's a lot of boilerplate, but binding directly to Models is not acceptable in any but the simplest scenarios; it's also not in the spirit of MVVM.

  2. ViewModels have sole responsibility for pushing changes. This obviously includes pushing changes to the server, but it could also include pushing changes to other components of the application.

    To do this, ViewModels might want to clone the Models they wrap so that they can provide transaction semantics to the rest of the app as they provide to the server (i.e. you can choose when to push changes to the rest of the app, which you cannot do if everyone directly binds to the same Model instance). Isolating changes like this requires still more work, but it also opens up powerful possibilities (e.g. undoing changes is trivial: just don't push them).

  3. ViewModels have a dependency on some kind of Data Service. The Data Service is an application component that sits between the data store and the consumers and handles all communication between them. Whenever a ViewModel clones its Model it also subscribes to appropriate "data store changed" events that the Data Service exposes.

    This allows ViewModels to be notified of changes to "their" model that other ViewModels have pushed to the data store and react appropriately. With proper abstraction, the data store can also be anything at all (e.g. a WCF service in that specific application).

Workflow

  1. A ViewModel is created and assigned ownership of a Model. It immediately clones the Model and exposes this clone to the View. Having a dependency on the Data Service, it tells the DS that it wants to subscribe to notifications for updates this specific Model. The ViewModel does not know what it is that identifies its Model (the "primary key"), but it doesn't need to because that's a responsibility of the DS.

  2. When the user finishes editing they interact with the View which invokes a Command on the VM. The VM then calls into the DS, pushing the changes made to its cloned Model.

  3. The DS persists the changes and additionally raises an event that notifies all other interested VMs that changes to Model X have been made; the new version of the Model is supplied as part of the event arguments.

  4. Other VMs that have been assigned ownership of the same Model now know that external changes have arrived. They can now decide how to update the View having all pieces of the puzzle at hand (the "previous" version of the Model, which was cloned; the "dirty" version, which is the clone; and the "current" version, which was pushed as part of the event arguments).

Notes

  • The Model's INotifyPropertyChanged is used only by the View; if the ViewModel wants to know whether the Model is "dirty", it can always compare the clone to the original version (if it has been kept around, which I recommend if possible).
  • The ViewModel pushes changes to the Server atomically, which is good because it ensures that the data store is always in a consistent state. This is a design choice, and if you want to do things differently another design would be more appropriate.
  • The Server can opt to not raise the "Model changed" event for the ViewModel that was responsible for this change if the ViewModel passes this as a parameter to the "push changes" call. Even if it does not, the ViewModel can choose to do nothing if it sees that the "current" version of the Model is identical to its own clone.
  • With enough abstraction, changes can be pushed to other processes running on other machines as easily as they can be pushed to other Views in your shell.

Hope this helps; I can offer more clarification if required.

Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806
  • 2
    +1 for stating the MVVM fundamentals in short and clear terms. – PVitt May 04 '12 at 12:38
  • I created a proof of concept of this and I feel this is just the way I was looking for. You also convinced me to use viewmodels and more importantly made it clear _why_ I should use them. – ndsc May 04 '12 at 21:04
  • How would I handle changes made by the View that cannot be databound in this case? Expose methods on the ViewModel for the View to use? – ndsc May 05 '12 at 08:56
  • @ndsc: Glad this works for you. Re changes, pretty much anything can be databound with an appropriate `IValueConverter` and also keep in mind that you can build functionality into the VM specifically for this purpose. What kind of changes do you have in mind? – Jon May 05 '12 at 14:41
  • For instance: I'm binding a few mouse events that in turn need business logic to do calculations. The events are processed by the View and since the View has no access to the Model it will have to ask the ViewModel to pass on the request to the View. – ndsc May 06 '12 at 14:05
  • 1
    By the way: I tried working your suggestion to use cloning out. I just ran against some complexity which I take make it not worth to use cloning. My Models got a Parent/Child relationship and got navigational properties which need to be retained. Would you suggest using reflection to make the copies or have the ViewModel implement something like ModelChanged() and have the DS provide a list of properties that have changed (since the DS has access to an old copy when receiving the new model). – ndsc May 06 '12 at 14:08
  • +1 for taking your time to explain and not rushing the first to answer as most do! Thanks for the clear explanation as well. I couldn't really understand how to trigger changes on the model via the view model but now it all makes sense. – Andris Feb 19 '13 at 14:22
  • @Jon Can you perhaps have a look on that question: http://stackoverflow.com/questions/16947122/binding-to-model-or-viewmodel – Mare Infinitus Jun 05 '13 at 19:45
  • @Jon Thank you, I ended up with concept like this one, so I think, I'm on a good way. Anyway, I have a question: Can be your ViewModels Model changed through it's lifetime? – Tomas Petovsky Jan 20 '16 at 08:34
  • @TomasPetovsky sorry for late reply -- you mean have a viewmodel "own" a different model at some point during its lifetime? Certainly possible, although in my case not needed. Also, intuitively I would prefer to create a separate viewmodel and do the switch at the view level, changing viewmodels. – Jon Apr 01 '16 at 09:07
  • why are you cloning the model? Seems pretty bad performance and memory wise. – Steffen Winkler Oct 13 '16 at 09:56
  • @SteffenWinkler because it isn't bad either performance or memory wise. The model is small (say, in the KB range) and it's cloned as a direct response to user interaction ("clicks edit button"). Performance doesn't come into it unless we are dealing with huge objects or inside loops. – Jon Oct 13 '16 at 18:53
  • @Jon so you aren't talking deep copy/deep clone? Then I really don't get the point. The view model has to have a reference to the model, yes, but shouldn't all viewmodels share the same model instance? – Steffen Winkler Oct 13 '16 at 19:27
  • 1
    @SteffenWinkler deep vs shallow doesn't directly translate to large vs small. As for sharing the model instance, sure, that's a no-brainer *when you only use them for reading*. For writing, obviously business considerations can be more important than what the books suggest when teaching MVVM 101. When you hit rename on a file in your filesystem explorer app, would you like to see the name of the file everywhere else in the OS automatically update to reflect your as-yet unconfirmed keystrokes? – Jon Oct 14 '16 at 08:07
  • `deep vs shallow doesn't directly translate to large vs small.` eh, it does in most cases. Or at least in most cases I know. About the renaming: No, of course not. But those keystrokes aren't commited to the model at that point anyway. Only on pressing enter does the viewmodel send the changes to the model. – Steffen Winkler Oct 14 '16 at 13:11
  • @SteffenWinkler the viewmodel would then have to store the intermediate values somewhere. So effectively you would be cloning the model anyway. – Jon Oct 14 '16 at 13:14
  • @Jon I only hold a reference to the model instance and there are properties on the ViewModel that hold select values that are relevant for *that* view. I just don't get why you'd want multiple instances of the same model. – Steffen Winkler Oct 16 '16 at 11:08
  • @SteffenWinkler perhaps you don't "want" them per se, but if it's doable as it was in my case then it's a trivially repeatable move that gives you lots of flexibility. You definitely don't have to do it if it doesn't make sense in your scenario. – Jon Oct 16 '16 at 18:04
  • Perhaps you can implement `INotifyPropertyChanging` to get the intermediate value? I.e. when a value is about to change, you clone it. – l33t Jan 16 '18 at 20:54
7

I would suggest adding Controllers to the MVVM mix (MVCVM?) to simplify the update pattern.

The controller listens for changes at a higher level and propagates changes between the Model and ViewModel.

The basic rules to keep things clean are:

  • ViewModels are just dumb containers that hold a certain shape of data. They do not know where the data comes from or where it is displayed.
  • Views display a certain shape of data (via bindings to a view model). They do not know where the data comes from, only how to display it.
  • Models supply real data. They do not know where it is consumed.
  • Controllers implement logic. Things like supplying the code for ICommands in VMs, listening for changes to data etc. They populate VMs from Models. It makes sense to have them listen for VM changes and update the Model.

As mentioned in another answer your DataContext should be the VM (or property of it), not the model. Pointing at a DataModel makes it hard to separate concerns (e.g. for Test Driven Development).

Most other solutions put logic in ViewModels which is "not right", but I see the benefits of controllers overlooked all the time. Darn that MVVM acronym! :)

iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • In this architecture, do controllers listen to Model property changes (and collection changes) and update the VMs accordingly? – redcurry Sep 15 '17 at 14:10
  • @redcurry: Yes, all logic is in the controller in this pattern.It makes all decisions on what to do with data changes. – iCollect.it Ltd Sep 16 '17 at 09:06
  • Thanks. I've been using this MVCVM or MVVMC pattern (at least my version of it), and it's been great. My view models don't have so many dependencies and responsibilities, and it's easier to implement undo functionality, logging, error reporting, etc. However, there aren't many (any?) examples of this pattern where one could see how others implement it. Do you know of any code available online (or that you can share) using this pattern? Thanks again. – redcurry Sep 16 '17 at 11:04
  • @redcurry: The only place we saw it in production was a 3rd party framework generator Sculture: http://sculpture.codeplex.com/ but it it just feels right. – iCollect.it Ltd Sep 19 '17 at 16:03
1

binding model to view directly only works if the model implement INotifyPropertyChanged interface. (eg. your Model generated by Entity Framework)

Model implement INotifyPropertyChanged

you can do this.

public interface IModel : INotifyPropertyChanged //just sample model
{
    public string Title { get; set; }
}

public class ViewModel : NotificationObject //prism's ViewModel
{
    private IModel model;

    //construct
    public ViewModel(IModel model)
    {
        this.model = model;
        this.model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
    }

    private void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Title")
        {
            //Do something if model has changed by external service.
            RaisePropertyChanged(e.PropertyName);
        }
    }
    //....more properties
}

ViewModel as DTO

if Model implements INotifyPropertyChanged(it depends) you may use it as DataContext in most cases. but in DDD most MVVM model will be considered as EntityObject not a real Domain's Model.

more efficient way is to use ViewModel as DTO

//Option 1.ViewModel act as DTO / expose some Model's property and responsible for UI logic.
public string Title
{
    get 
    {
        // some getter logic
        return string.Format("{0}", this.model.Title); 
    }
    set
    {
        // if(Validate(value)) add some setter logic
        this.model.Title = value;
        RaisePropertyChanged(() => Title);
    }
}

//Option 2.expose the Model (have self validation and implement INotifyPropertyChanged).
public IModel Model
{
    get { return this.model; }
    set
    {
        this.model = value;
        RaisePropertyChanged(() => Model);
    }
}

both of ViewModel's properties above can be used for Binding while not breaking MVVM pattern (pattern != rule) it really depends.

One more thing.. ViewModel has dependency on Model. if Model can be changed by external service/environment. it's "global state" that make things complicate.

aifarfa
  • 3,939
  • 2
  • 23
  • 35
0

If your only problem is that changes from the server get immediately re-saved, why not do something like the following:

//WARNING: typed in SO window
public class ViewModel
{
    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            if (value != _title) 
            {
                _title = value;
                this.OnPropertyChanged("Title");
                this.BeginSaveToServer();
            }
        }
    }

    public void UpdateTitleFromServer(string newTitle)
    {
        _title = newTitle;
        this.OnPropertyChanged("Title"); //alert the view of the change
    }
}

This code manually alerts the view of the property change from the server without going through the property setter and therefore without invoking the "save to server" code.

Steve Greatrex
  • 15,789
  • 5
  • 59
  • 73
  • The DataContext for the View is the Model though. Do you suggest keeping separate properties on the ViewModel? I've chosen to bind directly to the Model so I dont need to duplicate the properties. – ndsc May 03 '12 at 18:41
  • Given that you want different, view-specific behaviour for the `Title` property (saving to the server) then I would suggest that it belongs on the view model. You could implement it so that, instead of using a `string` to store it's value you just use the model property (e.g. `get { return _model.Title; }` etc) – Steve Greatrex May 03 '12 at 18:46
  • I do need the Model to notify the ViewModel about changes though. Directly passing the property values to the Model will cause it to echo back the View. – ndsc May 03 '12 at 18:50
  • What will be causing the model changes that the view will need to be notified of? Just the server as described above? – Steve Greatrex May 03 '12 at 18:52
  • Both the server as user interaction with the View. – ndsc May 03 '12 at 18:55
  • In that case, have the view model listen to the `PropertyChanged` event on the model and then raise it's own event for the `Title` field when the model is updated. – Steve Greatrex May 03 '12 at 18:59
  • You better seperate your model from the server connection and have the ViewModel control this connection. – PVitt May 04 '12 at 12:58
0

The reason you have this problem is because your model doesn't know whether it is dirty or not.

string Title {
  set {
    this._title = value;
    this._isDirty = true; // ??!!
  }
}}

The solution is to copy the server changes via a separate method:

public void CopyFromServer(Model serverCopy)
{
  this._title = serverCopy.Title;
}
Chui Tey
  • 5,436
  • 2
  • 35
  • 44
  • 2
    The model shouldn't know whether it is dirty or not. That's why the ViewModel is there. – PVitt May 04 '12 at 13:00