MVVM: liaison au Model tout en gardant le Model synchronisé avec une version de serveur

j'ai passé du temps à essayer de trouver une solution élégante pour le défi suivant. J'ai été incapable de trouver une solution qui soit plus qu'un piratage autour du problème.

j'ai une configuration simple D'une vue, D'un modèle et D'un modèle. Je vais garder cela très simple pour les raisons de l'explication.

  • le Model possède une propriété unique appelée Title de chaîne de caractères.
  • Le Model est le texte de référence pour le View .
  • le View a une base de données de TextBlock sur le modèle.
  • le ViewModel a une méthode appelée Save() qui sauvera le Model à un Server
  • le Server peut pousser les modifications apportées au Model

jusqu'à présent tout va bien. Maintenant, il y a deux réglages que je dois faire afin de garder le modèle est synchronisé avec un Server . Le type de serveur n'est pas important. Juste savoir que je dois appeler Save() afin de pousser le modèle au Server.

ajustement 1:

  • la propriété Model.Title devra appeler RaisePropertyChanged() afin de traduire les changements apportés au Model par le Server au View . Cela fonctionne bien depuis le Model est le Texte de référence pour le View

pas trop mal.

ajustement 2:

  • la prochaine étape est d'appeler Save() pour enregistrer les changements effectués du View au Model sur le Server . C'est là que je suis coincé. Je peux gérer l'événement Model.PropertyChanged sur le ViewModel qui appelle Save () quand le modèle est modifié, mais cela fait écho aux modifications faites par serveur.

je suis à la recherche d'une solution élégante et logique et je suis prêt à changer mon architecture si cela a du sens.

26
demandé sur ndsc 2012-05-03 22:24:24

5 réponses

dans le passé, j'ai écrit une application qui prend en charge l'édition" en direct " des objets de données à partir de plusieurs endroits: de nombreuses instances de l'application peuvent éditer le même objet en même temps, et lorsque quelqu'un pousse des changements sur le serveur, tout le monde est averti et (dans le scénario le plus simple) voit ces changements immédiatement. Voici un résumé de la façon dont il a été conçu.

Setup

  1. points de Vue toujours de les lier à des ViewModels. Je sais que c'est beaucoup de boilerplate, mais lier directement aux modèles n'est pas acceptable dans tous les scénarios sauf les plus simples; ce n'est pas non plus dans l'esprit du MVVM.

  2. Viewmodel ont seul la responsabilité de pousser des changements. Cela inclut évidemment de pousser les modifications vers le serveur, mais cela pourrait aussi inclure de pousser les modifications vers d'autres composants de l'application.

    pour ce faire, Modèles de vue pourrait vouloir cloner les modèles qu'ils enveloppent de sorte qu'ils puissent fournir la sémantique de transaction au reste de l'application comme ils fournissent au serveur (i.e. vous pouvez choisir quand pousser les changements au reste de l'application, ce que vous ne pouvez pas faire si tout le monde se lie directement à la même instance de modèle). Isoler des changements comme celui-ci nécessite encore plus de travail, mais il ouvre également de puissantes possibilités (par exemple, annuler des changements est trivial: il suffit de ne pas les pousser).

  3. les Modèles de vue dépendent d'un type de service de données. Le service de données est une composante d'application qui se trouve entre le magasin de données et les consommateurs et gère toute communication entre eux. Chaque fois qu'un ViewModel clone son modèle, il s'abonne également à des événements appropriés "data store changed" que le service de données expose.

    cela permet aux modèles de vue d'être informés des changements à "leur" modèle que les autres modèles de vue ont poussé à la mémoire de données et réagir de manière appropriée. Avec une abstraction appropriée, le data store peut aussi être n'importe quoi (par exemple un service WCF dans cette application spécifique).

Flux de travail

  1. un modèle de vue est créé et la propriété d'un modèle lui est assignée. Il clone immédiatement le Modèle et expose ce clone à la vue. Ayant une dépendance sur le service de données, il dit au DS qu'il veut abonnez-vous aux notifications pour mettre à jour Ce modèle spécifique. Le ViewModel ne sait pas ce qui identifie son modèle (la "clé primaire"), mais il n'en a pas besoin car c'est une responsabilité du DS.

  2. lorsque l'Utilisateur termine l'édition ils interagissent avec la vue qui invoque une commande sur la VM. La VM appelle alors dans le DS, poussant les changements faits à son modèle cloné.

  3. La DS persiste les changements et soulève en outre un événement qui avise tous les autres VMs intéressés que des changements au Modèle X ont été faits; la nouvelle version du modèle est fournie dans le cadre des arguments d'événement.

  4. les autres MV auxquels on a attribué la propriété du même modèle savent maintenant que des changements externes sont arrivés. Ils peuvent maintenant décider comment mettre à jour la vue ayant toutes les pièces du puzzle à portée de main (la version "précédente" du modèle, qui a été cloné; la version" sale", qui est le clone; et la version" courante", qui a été poussée dans le cadre des arguments d'événement).

Notes

  • le modèle INotifyPropertyChanged n'est utilisé que par la vue; si le ViewModel veut savoir si le modèle est "sale", il peut toujours comparer le clone à la version originale (s'il a été conservé, ce que je recommande si possible).
  • Le Modèle De Vue pousse les changements vers le serveur atomiquement, ce qui est bien car cela garantit que le stockage de données est toujours dans un état cohérent. C'est un choix de conception, et si vous voulez faire les choses différemment d'une autre conception serait plus approprié.
  • le serveur peut choisir de ne pas soulever l'événement" Model changed "pour le ViewModel responsable de ce changement si le ViewModel passe this comme paramètre à l'appel" push changes". Même si elle ne le fait pas, le ViewModel peut choisir de ne rien faire si elle voit que la version "actuelle" du Modèle est identique à son propre clone.
  • avec assez d'abstraction, les changements peuvent être poussés vers d'autres processus tournant sur d'autres machines aussi facilement qu'ils peuvent être poussés vers d'autres vues dans votre shell.

J'espère que cela aidera; je peux apporter plus de précisions si nécessaire.

63
répondu Jon 2012-05-04 10:04:54

je suggère d'ajouter des contrôleurs au mix MVVM (MVCVM? pour simplifier le schéma de mise à jour.

le contrôleur écoute les changements à un niveau supérieur et propage les changements entre le Modèle et le modèle de vue.

les règles de base pour garder les choses propres sont:

  • les Modèles de vue sont des conteneurs muets qui contiennent une certaine forme de données. ils ne savent pas d'où viennent les données ni où elles se trouvent. afficher.
  • Les vues
  • affichent une certaine forme de données (via des fixations à un modèle de vue). ils ne savent pas d'où viennent les données, seulement comment les afficher.
  • Les modèles
  • fournissent des données réelles. Ils ne savent pas où il est consommé.
  • Les contrôleurs
  • implémentent la logique. Des choses comme fournir le code pour ICommands dans VMs, écouter les changements de données, etc. Ils peuplent VMs à partir de Modèles. Il est logique qu'ils écoutent les changements de VM et mettent à jour le modèle.

comme mentionné dans une autre réponse votre DataContext devrait être la VM (ou la propriété de celle-ci), pas le modèle. Pointer sur un DataModel rend difficile la séparation des préoccupations (par exemple pour un développement piloté par des tests).

la plupart des autres solutions mettent de la logique dans les Modèles de vue ce qui n'est" pas juste", mais je vois les avantages des contrôleurs négligés tout le temps. Darn cet acronyme MVVM! :)

6
répondu Gone Coding 2012-08-03 09:34:50

modèle de liaison pour voir directement ne fonctionne que si le modèle implémente INotifyPropertyChanged interface. (eg. votre modèle généré par Entity Framework)

modèle implémenter INotifyPropertyChanged

vous pouvez le faire.

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

si Model implémente INotifyPropertyChanged(cela dépend) Vous pouvez l'utiliser comme Data-texte dans la plupart des cas. mais en DDD, la plupart des modèles MVVM seront considéré comme EntityObject pas un vrai modèle de domaine.

le moyen le plus efficace est d'utiliser ViewModel comme 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);
    }
}

les deux propriétés de ViewModel ci-dessus peuvent être utilisées pour lier tout en ne cassant pas le motif MVVM (pattern != règle) cela dépend vraiment.

encore une chose.. ViewModel a dépendance sur le Modèle. si le modèle peut être modifié par un service ou un environnement externe. c'est" Global state " qui complique les choses.

1
répondu aifarfa 2012-05-04 06:49:09

si votre seul problème est que les modifications du serveur sont immédiatement sauvegardées, pourquoi ne pas faire quelque chose comme ceci:

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

ce code alerte manuellement la vue du changement de propriété depuis le serveur sans passer par le setter de propriété et donc sans invoquer le code" save to server".

0
répondu Steve Greatrex 2012-05-03 18:29:34

la raison pour laquelle vous avez ce problème est que votre modèle ne sait pas s'il est sale ou non.

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

la solution est de copier les modifications du serveur via une méthode séparée:

public void CopyFromServer(Model serverCopy)
{
  this._title = serverCopy.Title;
}
0
répondu Chui Tey 2012-05-04 05:52:35