Вопрос по c#, mvvm, silverlight – MVVM: привязка к модели при одновременной синхронизации модели с версией сервера

28

Я потратил довольно много времени, чтобы попытаться найти элегантное решение для следующей задачи. Мне не удалось найти решение, которое больше, чем взломать эту проблему.

У меня есть простая настройка View, ViewModel и Model. Я сделаю это очень просто ради объяснения.

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

Все идет нормально. Теперь нужно сделать две корректировки, чтобы синхронизировать модель сServer, Тип сервера не важен. Просто знаю, что мне нужно позвонитьSave() чтобы подтолкнуть модель к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

Не так уж плохо.

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.

Я ищу элегантное и логичное решение и готов изменить свою архитектуру, если это имеет смысл.

@ndsc Пожалуйста, взгляните глубже на вопрос, на который вы ссылаетесь, особенно мой ответ. Модель как DataContext резко нарушает шаблон MVVM. PVitt
Что-то странное ... использование модели в качестве Datacontext? это не фактический MVVM. во-первых, просто протестируйте ViewModel, как будто «View не существует». aifarfa
Приложение имеет большой пользовательский интерфейс, и я решил попробовать подход для перевода модели непосредственно в представление. Однако некоторые свойства обрабатываются ViewModel. Видеть это:stackoverflow.com/a/10324065/1120175 ndsc

Ваш Ответ

5   ответов
1

только если модель реализует интерфейс INotifyPropertyChanged. (например, ваша Модель, сгенерированная Entity Framework)

Model implement INotifyPropertyChanged

вы можете сделать это.

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

если Model реализует INotifyPropertyChanged (это зависит), вы можете использовать его как DataContext в большинстве случаев. но в DDD большинство моделей MVVM будут рассматриваться как EntityObject, а не как реальная модель домена.

Более эффективным способом является использование ViewModel в качестве 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);
    }
}

оба вышеуказанных свойства ViewModel могут быть использованы для привязки, при этом не нарушая шаблон MVVM (pattern! = rule), это действительно зависит.

Еще кое-что..  ViewModel зависит от модели. если Модель может быть изменена внешней службой / средой. это & quot; глобальное состо ние & quot; что усложняет ситуацию.

6

Чтобы упростить шаблон обновления.

Контроллер прослушивает изменения на более высоком уровне и распространяет изменения между Model и ViewModel.

Основные правила для поддержания чистоты:

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.

Как уже упоминалось в другом ответе вашDataContext должна быть виртуальная машина (или ее свойство), а не модель. Указание на DataModel затрудняет разделение проблем (например, для разработки через тестирование).

Большинство других решений помещают логику во ViewModels, что является «неправильным», но я вижу, что преимущества контроллеров все время упускаются из виду.Darn that MVVM acronym! :)

В этой архитектуре контроллеры слушают изменения свойств модели (и изменения коллекции) и обновляют виртуальные машины соответственно?
Благодарю. Я использовал этот паттерн MVCVM или MVVMC (по крайней мере, мою его версию), и это было здорово. Мои модели представлений не имеют так много зависимостей и обязанностей, и легче реализовать функции отмены, ведения журналов, отчетов об ошибках и т. Д. Однако существует не так много (каких-либо?) Примеров этого шаблона, где можно было бы увидеть, как другие реализуют это. Знаете ли вы какой-либо код, доступный в Интернете (или которым вы можете поделиться), используя этот шаблон? Еще раз спасибо.
@redcurry: Да, вся логика в контроллере по этому шаблону. Он принимает все решения о том, что делать с изменениями данных.
@redcurry: Единственное место, где мы видели его в производстве, был сторонний генератор фреймворков Sculture:sculpture.codeplex.com но это просто кажется правильным.
66

которое поддерживает «живой» редактирование объектов данных из разных мест: многие экземпляры приложения могут редактировать один и тот же объект одновременно, и когда кто-то отправляет изменения на сервер, все остальные получают уведомление и (в простейшем сценарии) немедленно видят эти изменения. Вот краткое изложение того, как оно было разработано.

Setup

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.

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).

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

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.

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.

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.

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.

Надеюсь это поможет; Я могу предложить больше разъяснений, если требуется.

@SteffenWinkler "глубокий" против "мелкий" не может напрямую переводиться в "большой" против "маленький". Что касается обмена экземпляром модели, конечно, это не простоwhen you only use them for reading, Для написания очевидно, что деловые соображения могут быть более важными, чем то, что предлагают книги при обучении MVVM 101. Когда вы нажимаете кнопку переименовать файл в приложении проводника файловой системы, вы хотите, чтобы имя файла везде в ОС автоматически обновлялось отражать ваши еще не подтвержденные нажатия клавиш?
Кстати: я попытался выработать ваше предложение использовать клонирование. Я просто столкнулся с некоторой сложностью, которую, как я понимаю, не стоит использовать клонирование. Мои Модели получили отношения Родитель / Ребенок и получили навигационные свойства, которые необходимо сохранить. Вы бы предложили использовать отражение, чтобы сделать копии, или чтобы ViewModel реализовал что-то вроде ModelChanged (), и чтобы DS предоставил список свойств, которые изменились (поскольку DS получает доступ к старой копии при получении новой модели). ndsc
+1 за краткое изложение основных принципов MVVM.
Как бы я обработал изменения, сделанные представлением, которые не могут быть привязаны к данным в этом случае? Предоставить методы в ViewModel для использования View? ndsc
Я создал доказательство концепции этого, и я чувствую, что это именно то, что я искал. Вы также убедили меня использовать viewmodels и, что более важно, дали понятьwhy Я должен использовать их. ndsc
0

что изменения с сервера немедленно восстанавливаются, почему бы не сделать что-то вроде следующего:

//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
    }
}

Этот код вручную предупреждает представление об изменении свойства с сервера, не проходя через установщик свойств и, следовательно, не вызывая & quot; сохранить на сервере & quot; код.

Оба сервера как пользовательское взаимодействие с View. ndsc
DataContext для представления является моделью, хотя. Вы предлагаете сохранить отдельные свойства в ViewModel? Я выбрал привязку непосредственно к модели, поэтому мне не нужно дублировать свойства. ndsc
Что будет причиной изменений модели, о которых необходимо будет уведомить представление? Просто сервер как описано выше?
Учитывая, что вы хотите другое, специфичное для представления поведение дляTitle свойство (сохранение на сервере), то я бы предположил, что оно принадлежит модели представления. Вы могли бы реализовать это так, чтобы вместо использованияstring чтобы сохранить его значение, вы просто используете свойство модели (например,get { return _model.Title; } так далее)
Мне нужна Модель, чтобы уведомить ViewModel об изменениях. Непосредственная передача значений свойств в модель вызовет эхо представления. ndsc
0

по которой у вас возникла эта проблема, заключается в том, что ваша модель не знает, грязная она или нет.

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

Решение состоит в том, чтобы скопировать изменения сервера с помощью отдельного метода:

public void CopyFromServer(Model serverCopy)
{
  this._title = serverCopy.Title;
}
Модель не должна знать, грязная она или нет. Вот почему ViewModel существует.

Похожие вопросы