web-dev-qa-db-fra.com

Comment mapper View Model vers Domain Model dans un POST action?

Chaque article trouvé sur Internet sur l'utilisation de ViewModels et sur l'utilisation de Automapper donne les indications du mappage de direction "Contrôleur -> Vue". Vous prenez un modèle de domaine avec toutes les listes de sélection dans un ViewModel spécialisé et vous le transmettez à la vue. C'est clair et bien.
La vue a une forme et finalement nous sommes dans l'action POST. Ici, tous les modèles de reliure arrivent avec [évidemment] un autre View Model qui est [évidemment] lié au ViewModel d'origine au moins dans la partie des conventions de dénomination à des fins de liaison et de validation .

Comment mappez-vous à votre modèle de domaine?

Que ce soit une action d’insertion, nous pourrions utiliser le même Automapper. Mais si c'était une action de mise à jour? Nous devons extraire notre entité de domaine du référentiel, mettre à jour ses propriétés en fonction des valeurs du ViewModel et enregistrer dans le référentiel.

ADDENDA 1 (9 février 2010): Parfois, l'attribution des propriétés du modèle ne suffit pas. Des actions doivent être entreprises contre le modèle de domaine en fonction des valeurs de View Model. C'est-à-dire que certaines méthodes doivent être appelées sur le modèle de domaine. Il devrait probablement exister une sorte de couche de service d’application faisant office de contrôleur et de domaine afin de traiter les modèles de vue ...


Comment organiser ce code et où le placer pour atteindre les objectifs suivants?

  • garder les contrôleurs minces
  • honorer la pratique SoC
  • suivre les principes de conception par domaine
  • être sec
  • à suivre ...
84
Anthony Serdyukov

J'utilise une interface IBuilder et je l'implémente à l'aide de ValueInjecter

public interface IBuilder<TEntity, TViewModel>
{
      TEntity BuildEntity(TViewModel viewModel);
      TViewModel BuildViewModel(TEntity entity);
      TViewModel RebuildViewModel(TViewModel viewModel); 
}

... (implémentation) RebuildViewModel appelle simplement BuildViewModel(BuilEntity(viewModel))

[HttpPost]
public ActionResult Update(ViewModel model)
{
   if(!ModelState.IsValid)
    {
       return View(builder.RebuildViewModel(model);
    }

   service.SaveOrUpdate(builder.BuildEntity(model));
   return RedirectToAction("Index");
}

d'ailleurs je n'écris pas ViewModel j'écris Input parce que c'est beaucoup plus court, mais que ce n'est pas vraiment important
J'espère que ça aide

Mise à jour: J'utilise cette approche maintenant dans/ ProDinner, l'application de démonstration ASPC MVC , , Elle s'appelle maintenant IMapper. 

36
Omu

Des outils tels qu'AutoMapper peuvent être utilisés pour mettre à jour un objet existant avec les données d'un objet source. L'action du contrôleur pour la mise à jour peut ressembler à:

[HttpPost]
public ActionResult Update(MyViewModel viewModel)
{
    MyDataModel dataModel = this.DataRepository.GetMyData(viewModel.Id);
    Mapper<MyViewModel, MyDataModel>(viewModel, dataModel);
    this.Repostitory.SaveMyData(dataModel);
    return View(viewModel);
}

En dehors de ce qui est visible dans l'extrait ci-dessus:

  • Les données POST permettant d'afficher le modèle + la validation sont effectuées dans ModelBinder (pourraient être accompagnées de liaisons personnalisées)
  • La gestion des erreurs (c'est-à-dire la capture des exceptions d'accès aux données levées par le référentiel) peut être effectuée par le filtre [HandleError]

L'action du contrôleur est assez mince et les problèmes sont séparés: les problèmes de mappage sont traités dans la configuration d'AutoMapper, la validation est effectuée par ModelBinder et l'accès aux données par le référentiel.

7
PanJanek

Je voudrais dire que vous réutilisez le terme ViewModel dans les deux sens de l’interaction client. Si vous avez lu suffisamment de code ASP.NET MVC dans la nature, vous avez probablement déjà constaté la distinction entre ViewModel et EditModel. Je pense que c'est important.

Un ViewModel représente toutes les informations requises pour rendre une vue. Cela pourrait inclure des données rendues dans des emplacements statiques non interactifs et des données uniquement pour effectuer un contrôle afin de décider du rendu exact. Une action Controller GET est généralement chargée d’emballer le ViewModel pour sa vue.

Un EditModel (ou peut-être un ActionModel) représente les données nécessaires à l'exécution de l'action que l'utilisateur souhaitait effectuer pour ce POST. Donc, un EditModel essaie vraiment de décrire une action. Cela exclura probablement certaines données du ViewModel et, bien que liées, je pense qu'il est important de réaliser qu'elles sont en effet différentes.

Une idée

Cela dit, vous pouvez très facilement avoir une configuration AutoMapper pour passer de Modèle -> ViewModel et une autre pour passer de EditModel -> Modèle. Ensuite, les différentes actions du contrôleur doivent simplement utiliser AutoMapper. Hell EditModel pourrait avoir une fonction pour valider ses propriétés par rapport au modèle et appliquer ces valeurs au modèle lui-même. Cela ne fait rien d'autre et vous avez ModelBinders dans MVC pour mapper de toute façon la demande à EditModel. 

Une autre idée

Au-delà de ce à quoi je réfléchissais récemment, ce type de solution découle de l'idée d'un ActionModel, à savoir que ce que le client vous renvoie est en réalité la description de plusieurs actions effectuées par l'utilisateur et non pas d'un seul gros fichier de données. Cela nécessiterait certainement un peu de Javascript côté client, mais l'idée est intrigante, je pense.

Essentiellement, lorsque l'utilisateur exécute des actions sur l'écran que vous avez présenté, Javascript commence à créer une liste d'objets d'action. Un exemple est peut-être que l'utilisateur se trouve sur un écran d'informations d'employé. Ils mettent à jour le nom de famille et ajoutent une nouvelle adresse car l'employé vient de se marier. Sous les couvertures, cela produit les objets ChangeEmployeeName et AddEmployeeMailingAddress dans une liste. L'utilisateur clique sur "Enregistrer" pour valider les modifications et soumet la liste de deux objets, chacun contenant uniquement les informations nécessaires à l'exécution de chaque action.

Vous auriez besoin d'un ModelBinder plus intelligent que celui par défaut, mais un bon sérialiseur JSON devrait pouvoir gérer le mappage des objets d'action côté client sur ceux du côté serveur. Les serveurs (si vous êtes dans un environnement à deux niveaux) peuvent facilement avoir des méthodes qui complètent l'action sur le modèle avec lequel ils travaillent. Ainsi, l'action du contrôleur finit par obtenir un ID pour l'instance de modèle à extraire et une liste d'actions à effectuer. Ou les actions ont le id en eux pour les garder très séparés.

Alors peut-être que quelque chose comme ceci est réalisé du côté serveur:

public interface IUserAction<TModel>
{
     long ModelId { get; set; }
     IEnumerable<string> Validate(TModel model);
     void Complete(TModel model);
}

[Transaction] //just assuming some sort of 2-tier with transactions handled by filter
public ActionResult Save(IEnumerable<IUserAction<Employee>> actions)
{
     var errors = new List<string>();
     foreach( var action in actions ) 
     {
         // relying on ORM's identity map to prevent multiple database hits
         var employee = _employeeRepository.Get(action.ModelId);
         errors.AddRange(action.Validate(employee));
     }

     // handle error cases possibly rendering view with them

     foreach( var action in editModel.UserActions )
     {
         var employee = _employeeRepository.Get(action.ModelId);
         action.Complete(employee);
         // against relying on ORMs ability to properly generate SQL and batch changes
         _employeeRepository.Update(employee);
     }

     // render the success view
}

Cela rend vraiment l’opération de restitution assez générique puisque vous comptez sur votre ModelBinder pour obtenir l’instance IUserAction correcte et sur votre instance IUserAction pour exécuter la bonne logique ou un appel (plus probable) au modèle avec les informations.

Si vous vous trouviez dans un environnement à 3 niveaux, l'IUserAction pourrait simplement être transformé en un simple DTO à traverser la frontière et exécuté selon une méthode similaire sur la couche d'application. En fonction de la manière dont vous faites cette couche, elle peut être très facilement divisée et rester dans une transaction (on pense à la demande/réponse d’Agatha et à la valorisation de la carte d’identité de DI et de NHibernate).

En tout cas, je suis sûr que ce n’est pas une idée parfaite, il faudrait un certain JS côté client pour le gérer, et je n’ai pas encore été capable de faire un projet pour voir comment il se déroulera, mais le message essayait de penser à la y aller et revenir, alors je pensais que je donnerais mes pensées. J'espère que cela aide et j'aimerais connaître d'autres moyens de gérer les interactions.

5
Sean Copenhaver

Vous n'avez pas besoin de mapper le modèle de vue sur le domaine car votre modèle de vue peut être créé davantage que le modèle de domaine. Viewmodels optimisés pour screen (ui) et différents du modèle de domaine.

http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/

0
oguzh4n