54 votes

ASP.NET MVC Architecture : ViewModel par composition, héritage ou duplication ?

J'utilise ASP.NET MVC 3 et Entity Framework 4.1 Code First.

Disons que j'ai un User entité :

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }        
}

En l'éditant dans mon UserController Je veux ajouter un PasswordConfirmation et vérifiez que PasswordConfirmation == Password

1. Par composition

Mon premier essai était :

public class EditUserModel
{
    [Required]
    public User User { get; set; }

    [Compare("User.Password", ErrorMessage = "Passwords don't match.")]
    public string PasswordConfirmation { get; set; }
}

Dans ce cas, la validation côté client fonctionne mais ( Editar: la validation côté client fonctionnant était une coïncidence). ne fonctionne pas y el la validation côté serveur échoue avec le message suivant : Impossible de trouver une propriété nommée User.Password

Editar: Je pense que la meilleure solution, dans ce cas, serait de créer un fichier personnalisé CompareAttribute

Mise en œuvre de IValidatableObject

public class EditUserModel : IValidatableObject
{
    [Required]
    public User User { get; set; }
    public string PasswordConfirmation { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if(this.PasswordConfirmation != this.User.Password)
            return new[] { new ValidationResult("Passwords don't match", new[] { "PasswordConfirmation " }) };

        return new ValidationResult[0];
    }
}

Dans ce cas, le la validation côté serveur fonctionne mais le la validation côté client ne fonctionne pas plus. Mise en œuvre de IClientValidatable semble un peu trop compliqué et je préfère ne pas avoir de validation côté client dans ce cas.

2. Par héritage

public class EditUserModel : User
{
    [Compare("Password", ErrorMessage = "Passwords don't match.")]
    public string PasswordConfirmation  { get; set; }
}

En essayant d'enregistrer directement EditUserModel en utilisant EF, cela ne fonctionne pas, j'obtiens un message d'erreur à propos de l'utilisation de l'EF. EditUserModel donc j'utilise AutoMapper pour passer de User a EditUserModel et en arrière. Cette solution travaux mais c'est plus complexe car je dois convertir le modèle en modèle de vue et inversement.

3. Par duplication

(Proposé par Malte Clasen)

Le modèle de vue aurait toutes les propriétés du modèle, plus des propriétés supplémentaires. AutoMapper peut être utilisé pour convertir l'un en l'autre.

public class EditUserModel {    
  public string Name { get; set; }    
  public string Email { get; set; }    
  public string Password { get; set; }   
  [Compare("Password", ErrorMessage = "Passwords don't match.")]     
  public string ConfirmPassword { get; set; }        
}

C'est la solution que je préfère le moins à cause de la duplication du code (DRY).

Questions

Quels sont les avantages et les inconvénients de l'héritage, de la composition et de la duplication dans ce cas ?

Existe-t-il un moyen simple d'avoir une validation côté client et côté serveur sans devoir convertir le modèle en modèle de vue et inversement ?

26voto

Josh Points 3236

Ayant déjà été confronté à cette question, j'ai, à plusieurs reprises, opté pour les trois. En général, la plupart des opinions que j'ai vues favorisent la duplication dans un projet MVC, avec un ViewModel construit spécifiquement pour chaque vue. De cette manière, la convention que vous utiliseriez est quelque chose comme UserDetailsViewModel y UserCreateViewModel . Comme vous l'avez dit, à ce stade, AutoMapper ou un autre outil de mappage automatique serait utilisé pour convertir les objets de votre domaine en ces ViewModels plats.

Si, moi aussi, je n'aime pas répéter le code, je n'aime pas non plus polluer mes objets de domaine avec des attributs de validation ou d'autres attributs spécifiques à la vue. Un autre avantage, bien qu'il soit admis que presque personne n'aura jamais à y faire face (quoi qu'en disent tous les pros), est que vous pouvez manipuler vos objets de domaine d'une certaine manière sans nécessairement manipuler vos ViewModels. Je mentionne cela parce que c'est couramment cité, pas parce que cela a beaucoup de poids pour moi.

Enfin, l'utilisation d'un ViewModel vraiment plat permet un balisage plus propre. Lorsque j'ai utilisé la composition, j'ai souvent commis des erreurs en créant des éléments HTML avec des noms du genre User.Address.Street . Un ViewModel plat réduit au moins mes chances de le faire (je sais, je pourrais toujours utiliser les routines HtmlHelper pour créer des éléments, mais ce n'est pas toujours faisable).

De toute façon, mes projets récents ont également nécessité des ViewModels séparés. Ils sont tous basés sur NHibernate, et l'utilisation de proxies sur les objets NHibernate ne permet pas de les utiliser directement pour les vues.

Mise à jour - Voici un bon article auquel je me suis déjà référé par le passé : http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx

6voto

Malte Clasen Points 3989

Vous pourriez également envisager des classes indépendantes pour les modèles de domaine et de vue, dans ce cas par exemple

public class EditUserModel {    
  public string Name { get; set; }    
  public string Email { get; set; }    
  public string Password { get; set; }        
  public string ConfirmPassword { get; set; }        
}

si l'Id est stocké dans l'url. Si vous voulez éviter la copie manuelle entre les instances de User et EditorUserModel, AutoMapper peut vous aider. De cette façon, vous pouvez facilement découpler la chaîne de mots de passe dans votre modèle de vue du hachage du mot de passe dans votre modèle de domaine.

2voto

Mauro Ciancio Points 100

J'ai essayé de résoudre ce problème et j'ai trouvé une solution qui n'implique pas de dupliquer le code. C'est une sorte de solution de contournement mais, à mon avis, elle est meilleure que les autres solutions proposées.

Vous avez le modèle utilisateur avec toute la validation :

public class UserModel
{
    [Required]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }        
}

Vous composez le modèle précédent avec un nouveau modèle

public class EditUserModel
{
    public UserModel User { get; set; }

    [Required]
    public string PasswordConfirmation { get; set; }
}

L'astuce est dans l'action, vous pourriez recevoir plus d'un modèle :

[HtttPost]
public ActionResult UpdateInformation(UserModel user, EditUserModel editUserModel) {
    if (ModelState.IsValid) {
         // copy the inner model to the outer model, workaround here:
         editUserModel.User = user
         // do whatever you want with editUserModel, it has all the needed information
    }
}

De cette façon, la validation fonctionne comme prévu.

J'espère que cela vous aidera.

1voto

Varun Vohra Points 2083

Je n'utilise pas trop les modèles d'entités, je préfère les modèles LINQ - SQL, donc cela peut être incorrect :

Pourquoi ne pas utiliser une classe de méta-données qui est appliquée à l'Entité ? Avec LINQ - SQL, les méta-données attribuées sont prises en considération pour la validation tant du côté client que du côté serveur.

D'après ce que je comprends, l'application d'un attribut [MetaDataType] est similaire à l'héritage, sauf qu'elle fonctionne sans mettre en œuvre une nouvelle classe (modèle) pour les modifications de l'entité de base.

Vous pouvez également essayer de créer un attribut personnalisé. Je l'ai fait une fois dans un but similaire. Essentiellement un drapeau qui indique la persistance d'un membre.

J'aurais donc une entité définie comme suit :

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }     

    [DoNotPersist]   
    public string ConfirmPassword {get; set;}

}

De plus, je ne sais pas ce que vous faites pour stocker les données, mais j'ai intégré une surcharge dans les fonctions OnInserting, OnEditing, OnDeleting pour mon DataContext, ce qui a essentiellement supprimé tous les membres ayant mon attribut personnalisé.

J'aime cette méthode simple parce que nous utilisons beaucoup de données temporaires, plutôt algorithmiques pour chaque modèle (construction de bonnes IU pour la Business Intelligence) qui ne sont pas sauvegardées dans la base de données mais qui sont utilisées partout à l'intérieur des fonctions du modèle, des contrôleurs, etc - nous utilisons donc l'injection de dépendances dans tous les référentiels de modèles et les contrôleurs et nous avons ainsi tous ces points de données supplémentaires pour chaque table avec lesquels jouer.

J'espère que cela vous aidera !

PS:- Composition vs Héritage - cela dépend vraiment de l'utilisateur cible de l'application. S'il s'agit d'une application intranet où la sécurité est moins importante et où l'environnement de l'utilisateur et du navigateur est contrôlé, il suffit d'utiliser la validation côté client, c'est-à-dire la composition.

0voto

Jakub Konecki Points 28852

Je privilégierais la composition à l'héritage.

Dans le cas de votre mot de passe utilisateur, il semble que vous stockez en fait le mot de passe dans la table Users en texte clair, ce qui est TRÈS, TRÈS MAUVAIS.

Vous ne devez stocker qu'un hachage salé, et votre EditUserModel devrait avoir deux propriétés de chaîne pour le mot de passe et la confirmation du mot de passe, qui ne sont PAS les champs de votre table.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X