web-dev-qa-db-fra.com

Meilleures pratiques ViewModel

À partir de cette question , il semble logique de demander à un contrôleur de créer un ViewModel qui reflète plus précisément le modèle view tente de s'afficher, mais certaines conventions sont curieuses (je suis nouveau dans le modèle MVC, s'il n'était pas déjà évident).

Fondamentalement, j'avais les questions suivantes:

  1. J'aime normalement avoir une classe/fichier. Cela a-t-il un sens avec un ViewModel s'il est uniquement créé pour transférer des données d'un contrôleur à une vue?
  2. Si un ViewModel appartient à son propre fichier et que vous utilisez une structure de répertoire/projet pour que les choses restent séparées, d'où vient le ViewModel fichier appartient? Dans le répertoire Controllers ?

C'est fondamentalement ça pour le moment. J'aurais peut-être quelques questions supplémentaires à venir, mais cela me préoccupe depuis une heure environ et je peux sembler trouver des directives cohérentes ailleurs.

EDIT: En regardant l'exemple application NerdDinner sur CodePlex, il semble que les ViewModels font partie de la contrôleurs , mais cela me met encore mal à l’aise qu’ils ne soient pas dans leurs propres fichiers.

234
jerhinesmith

Je crée ce que j'appelle un "ViewModel" pour chaque vue. Je les mets dans un dossier appelé ViewModels dans mon projet Web MVC. Je les nomme d'après le contrôleur et l'action (ou la vue) qu'ils représentent. Par conséquent, si je dois transmettre des données à la vue SignUp du contrôleur d'appartenance, je crée une classe MembershipSignUpViewModel.cs et la place dans le dossier ViewModels.

Ensuite, j'ajoute les propriétés et méthodes nécessaires pour faciliter le transfert des données du contrôleur à la vue. J'utilise Automapper pour aller de mon ViewModel au modèle de domaine et vice-versa, si nécessaire.

Cela fonctionne également bien pour les ViewModels composites contenant des propriétés du type des autres ViewModels. Par exemple, si vous avez 5 widgets sur la page d'index du contrôleur d'appartenance et que vous avez créé un ViewModel pour chaque vue partielle, comment passez-vous les données de l'action Index aux partiels? Vous ajoutez une propriété à MembershipIndexViewModel de type MyPartialViewModel et lors du rendu du partiel, vous transmettriez Model.MyPartialViewModel.

En procédant ainsi, vous pouvez ajuster les propriétés partielles de ViewModel sans avoir à modifier la vue Index. Il ne fait toujours que passer dans Model.MyPartialViewModel, de sorte qu'il est moins probable que vous deviez passer par toute la chaîne des partiels pour réparer quelque chose lorsque vous ne faites que l'ajout d'une propriété au ViewModel partiel.

J'ajouterai également l'espace de noms "MyProject.Web.ViewModels" au fichier web.config afin de me permettre de les référencer dans n'importe quelle vue sans jamais ajouter une instruction d'importation explicite à chaque vue. Cela rend juste un peu plus propre.

208
Ryan Montgomery

Séparer les classes par catégorie (Contrôleurs, Modèles de vue, Filtres, etc.) n’a aucun sens.

Si vous souhaitez écrire du code pour la section Accueil de votre site Web (/), créez un dossier nommé Accueil et placez-y les contrôles HomeController, IndexViewModel, AboutViewModel, etc., ainsi que toutes les classes associées utilisées par les actions Accueil.

Si vous avez des classes partagées, comme un ApplicationController, vous pouvez le placer à la racine de votre projet.

Pourquoi séparer les éléments liés (HomeController, IndexViewModel) et garder ensemble les éléments qui n’ont aucune relation (HomeController, AccountController)?


J'ai écrit un blog post à propos de ce sujet.

122
Max Toro

Je conserve mes classes d’application dans un sous-dossier appelé "Core" (ou une bibliothèque de classes séparée) et utilise les mêmes méthodes que pour le modèle KIGG mais avec certains de légers changements pour rendre mes applications plus sèches.

Je crée une classe BaseViewData dans/Core/ViewData/où je stocke les propriétés communes à l'ensemble du site.

Après cela, je crée également toutes mes classes ViewData View dans le même dossier, qui dérivent ensuite de BaseViewData et possèdent des propriétés de vue spécifiques.

Ensuite, je crée un ApplicationController dont dérivent tous mes contrôleurs. ApplicationController a une méthode GetViewData générique, comme suit:

protected T GetViewData<T>() where T : BaseViewData, new()
    {
        var viewData = new T
        {
           Property1 = "value1",
           Property2 = this.Method() // in the ApplicationController
        };
        return viewData;
    }

Enfin, dans mon action Contrôleur, procédez comme suit pour créer mon modèle ViewData.

public ActionResult Index(int? id)
    {
        var viewData = this.GetViewData<PageViewData>();
        viewData.Page = this.DataContext.getPage(id); // ApplicationController
        ViewData.Model = viewData;
        return View();
    }

Je pense que cela fonctionne vraiment bien et garde vos vues nettes et vos contrôleurs maigres.

21
Mark

Une classe ViewModel est là pour encapsuler plusieurs éléments de données représentés par des instances de classes dans un objet facile à gérer que vous pouvez transmettre à votre vue.

Il serait logique d’avoir vos classes ViewModel dans leurs propres fichiers, dans leur propre répertoire. Dans mes projets, j'ai un sous-dossier du dossier Modèles appelé ViewModels. C’est là que mes ViewModels (par exemple ProductViewModel.cs) vivre.

14
JMS

Il n’existe aucun lieu propice pour conserver vos modèles. Vous pouvez les conserver dans une assemblée séparée si le projet est volumineux et qu’il existe de nombreux ViewModels (objets de transfert de données). Vous pouvez également les conserver dans un dossier séparé du projet de site. Par exemple, dans Oxite , ils sont placés dans le projet Oxite, qui contient également de nombreuses classes. Les contrôleurs dans Oxite sont déplacés vers un projet séparé et les vues sont également dans un projet séparé.
In CodeCampServer ViewModels sont nommés * Form et sont placés dans le projet d'interface utilisateur dans le dossier Modèles.
Dans MvcPress projet, ils sont placés dans le projet Data, qui contient également tout le code pour travailler avec la base de données et un peu plus (mais je n'ai pas recommandé cette approche, c'est juste pour un exemple )
Vous voyez donc qu'il y a beaucoup de points de vue. Je conserve généralement mes objets ViewModels (objets DTO) dans le projet de site. Mais lorsque j'ai plus de 10 modèles, je préfère les déplacer vers une assemblée séparée. Habituellement, dans ce cas, je déplace les contrôleurs pour séparer l’Assemblée également.
Une autre question est de savoir comment mapper facilement toutes les données du modèle à votre ViewModel. Je suggère de jeter un oeil à la bibliothèque AutoMapper . J'aime beaucoup ça fait tout le sale boulot pour moi.
Et je suggère également de regarder le projet SharpArchitecture . Il fournit une très bonne architecture pour les projets et contient beaucoup de cadres et de guides sympas et une grande communauté.

12
zihotki

voici un extrait de code de mes meilleures pratiques:

    public class UserController : Controller
    {
        private readonly IUserService userService;
        private readonly IBuilder<User, UserCreateInput> createBuilder;
        private readonly IBuilder<User, UserEditInput> editBuilder;

        public UserController(IUserService userService, IBuilder<User, UserCreateInput> createBuilder, IBuilder<User, UserEditInput> editBuilder)
        {
            this.userService = userService;
            this.editBuilder = editBuilder;
            this.createBuilder = createBuilder;
        }

        public ActionResult Index(int? page)
        {
            return View(userService.GetPage(page ?? 1, 5));
        }

        public ActionResult Create()
        {
            return View(createBuilder.BuildInput(new User()));
        }

        [HttpPost]
        public ActionResult Create(UserCreateInput input)
        {
            if (input.Roles == null) ModelState.AddModelError("roles", "selectati macar un rol");

            if (!ModelState.IsValid)
                return View(createBuilder.RebuildInput(input));

            userService.Create(createBuilder.BuilEntity(input));
            return RedirectToAction("Index");
        }

        public ActionResult Edit(long id)
        {
            return View(editBuilder.BuildInput(userService.GetFull(id)));
        }

        [HttpPost]
        public ActionResult Edit(UserEditInput input)
        {           
            if (!ModelState.IsValid)
                return View(editBuilder.RebuildInput(input));

            userService.Save(editBuilder.BuilEntity(input));
            return RedirectToAction("Index");
        }
}
6
Omu

Nous plaçons tous nos modèles de vue dans le dossier Modèles (toute notre logique métier se trouve dans un projet ServiceLayer distinct).

5
Beep beep

Personnellement, je suggérerais que si ViewModel soit tout sauf trivial, utilisez une classe séparée.

Si vous avez plus d'un modèle de vue, je suggère qu'il soit logique de le partitionner dans au moins un répertoire. Si le modèle de vue est partagé ultérieurement, l'espace de noms impliqué dans l'annuaire facilite le déplacement vers un nouvel assemblage.

4
Preet Sangha

Dans notre cas, nous avons les modèles avec les contrôleurs dans un projet distinct des vues.

En règle générale, nous avons essayé de déplacer et d'éviter la plupart des éléments ViewData ["..."] dans ViewModel, ce qui évite les conversions et les chaînes magiques, ce qui est une bonne chose.

ViewModel contient également certaines propriétés communes telles que les informations de pagination pour les listes ou les informations d'en-tête de la page pour dessiner des chapelures et des titres. À ce moment, la classe de base contient trop d'informations à mon avis et nous pouvons les diviser en trois parties, les informations les plus élémentaires et les plus nécessaires pour 99% des pages d'un modèle de vue de base, puis un modèle pour les listes et un modèle. pour les formulaires qui contiennent des données spécifiques pour ces scénarios et héritent de celui de base.

Enfin, nous implémentons un modèle de vue pour chaque entité afin de traiter les informations spécifiques.

2
Marc Climent

code dans le contrôleur:

    [HttpGet]
        public ActionResult EntryEdit(int? entryId)
        {
            ViewData["BodyClass"] = "page-entryEdit";
            EntryEditViewModel viewMode = new EntryEditViewModel(entryId);
            return View(viewMode);
        }

    [HttpPost]
    public ActionResult EntryEdit(Entry entry)
    {
        ViewData["BodyClass"] = "page-entryEdit";            

        #region save

        if (ModelState.IsValid)
        {
            if (EntryManager.Update(entry) == 1)
            {
                return RedirectToAction("EntryEditSuccess", "Dictionary");
            }
            else
            {
                return RedirectToAction("EntryEditFailed", "Dictionary");
            }
        }
        else
        {
            EntryEditViewModel viewModel = new EntryEditViewModel(entry);
            return View(viewModel);
        }

        #endregion
    }

code dans le modèle de vue:

public class EntryEditViewModel
    {
        #region Private Variables for Properties

        private Entry _entry = new Entry();
        private StatusList _statusList = new StatusList();        

        #endregion

        #region Public Properties

        public Entry Entry
        {
            get { return _entry; }
            set { _entry = value; }
        }

        public StatusList StatusList
        {
            get { return _statusList; }
        }

        #endregion

        #region constructor(s)

        /// <summary>
        /// for Get action
        /// </summary>
        /// <param name="entryId"></param>
        public EntryEditViewModel(int? entryId)
        {
            this.Entry = EntryManager.GetDetail(entryId.Value);                 
        }

        /// <summary>
        /// for Post action
        /// </summary>
        /// <param name="entry"></param>
        public EntryEditViewModel(Entry entry)
        {
            this.Entry = entry;
        }

        #endregion       
    }

projets:

  • DevJet.Web (le projet Web ASP.NET MVC)

  • DevJet.Web.App.Dictionary (un projet de bibliothèque de classes séparé)

    dans ce projet, j'ai créé des dossiers tels que: DAL, BLL, BO, VM (dossier des modèles d'affichage)

0
C.T.

Créez une classe de base de modèle de vue qui possède les propriétés généralement requises, telles que le résultat de l'opération et les données contextuelles. Vous pouvez également définir les rôles et les données utilisateur actuelles.

class ViewModelBase 
{
  public bool HasError {get;set;} 
  public string ErrorMessage {get;set;}
  public List<string> UserRoles{get;set;}
}

Dans la classe de contrôleur de base ont une méthode comme PopulateViewModelBase (), cette méthode remplira les données contextuelles et les rôles d’utilisateur. HasError et ErrorMessage, définissez ces propriétés s'il existe une exception lors de l'extraction de données du service/db. Liez ces propriétés à l'affichage pour afficher une erreur. Les rôles d'utilisateur peuvent être utilisés pour afficher la section masquée sur la vue en fonction des rôles.

Pour renseigner les modèles de vue dans différentes actions get, vous pouvez le rendre cohérent en utilisant un contrôleur de base avec la méthode abstraite FillModel.

class BaseController :BaseController 
{
   public PopulateViewModelBase(ViewModelBase model) 
{
   //fill up common data. 
}
abstract ViewModelBase FillModel();
}

Dans les contrôleurs

class MyController :Controller 
{

 public ActionResult Index() 
{
   return View(FillModel()); 
}

ViewModelBase FillModel() 
{ 
    ViewModelBase  model=;
    string currentAction = HttpContext.Current.Request.RequestContext.RouteData.Values["action"].ToString(); 
 try 
{ 
   switch(currentAction) 
{  
   case "Index": 
   model= GetCustomerData(); 
   break;
   // fill model logic for other actions 
}
}
catch(Exception ex) 
{
   model.HasError=true;
   model.ErrorMessage=ex.Message;
}
//fill common properties 
base.PopulateViewModelBase(model);
return model;
}
}
0
Ajay Kelkar