Кращі практики ViewModel


238

З цього питання , схоже, має сенс мати контролер для створення ViewModel який би більш точно відображав модель, яку подання намагається відобразити, але мені цікаво деякі умови (я новачок у шаблоні MVC , якщо це вже не було очевидно).

В основному у мене були такі питання:

  1. Я зазвичай люблю мати один клас / файл. Це має сенс із a ViewModel, якщо він створюється лише для передачі даних від контролера до подання?
  2. Якщо ViewModel належить до власного файлу, а ви використовуєте структуру каталогу / проекту, щоб зберігати речі окремо, куди належить файл ViewModel ? У каталозі Контролери ?

В основному це наразі. У мене може виникнути ще кілька питань, але це мене турбує протягом останньої години або близько того, і я можу, здається, знайти послідовне керівництво в інших місцях.

EDIT: Переглядаючи зразок програми NerdDinner на CodePlex, схоже, ViewModels є частиною контролерів , але мені все одно незручно, що вони не є у власних файлах.


66
Я б точно не назвав NerdDinner прикладом "найкращих практик". Ваша інтуїція вам добре служить. :)
Райан Монтгомері

Відповіді:


211

Я створюю те, що я називаю "ViewModel" для кожного перегляду. Я помістив їх у папку під назвою ViewModels у своєму веб-проекті MVC. Я називаю їх за контролером та дією (або переглядом), який вони представляють. Отже, якщо мені потрібно передати дані до подання SignUp на контролері Membership, я створюю клас MembershipSignUpViewModel.cs і поміщу їх у папку ViewModels.

Потім я додаю необхідні властивості та методи, щоб полегшити передачу даних від контролера до перегляду. Я використовую Automapper, щоб дістатися зі свого ViewModel до Моделі домену та знову, якщо необхідно.

Це також добре працює для складених ViewModels, які містять властивості, що відносяться до типу інших ViewModels. Наприклад, якщо у вас є 5 віджетів на сторінці індексу в контролері членства, і ви створили ViewModel для кожного часткового перегляду - як ви передаєте дані з дії Index в партії? Ви додаєте властивість до MembershipIndexViewModel типу MyPartialViewModel, і при наданні частки ви перейдете в Model.MyPartialViewModel.

Це дозволяє вам налаштувати часткові властивості ViewModel, не змінюючи перегляд індексу. Він все ще просто проходить у Model.MyPartialViewModel, тому менше шансів, що вам доведеться пройти весь ланцюжок партій, щоб щось виправити, коли все, що ви робите, - додавання властивості до часткового ViewModel.

Я також додаю простір імен "MyProject.Web.ViewModels" до web.config, щоб дозволити мені посилатись на них у будь-якому огляді, не додаючи явно заяву про імпорт для кожного перегляду. Просто робить його трохи чистішим.


3
Що робити, якщо ви хочете розмістити POST з часткового перегляду та повернути весь вигляд (у випадку помилки моделі)? У межах часткового перегляду ви не маєте доступу до батьківської моделі.
Cosmo

5
@Cosmo: Потім POST для дії, яка може повернути весь вигляд у випадку помилки моделі. На стороні сервера вам достатньо відтворити батьківську модель.
Томаш Ащан

А як щодо входу [POST] та входу [GET]? з різними моделями перегляду?
Барт Калікто

Зазвичай логін [GET] не викликає ViewModel, оскільки не потрібно завантажувати будь-які дані.
Andre Figueiredo

Чудова порада. Куди слід відправляти доступ, обробку даних та налаштування властивостей моделі / VM? У моєму випадку ми матимемо деякі дані, що надходять із локальної бази даних CMS, а деякі - із веб-служб, які потрібно буде обробити / маніпулювати, перш ніж встановлювати модель. Помістити все, що в контролері, стає досить безладним.
xr280xr

124

Separating classes by category (Controllers, ViewModels, Filters etc.) is nonsense.

Якщо ви хочете написати код для розділу «Домашня сторінка» вашого веб-сайту (/), тоді створіть папку з назвою «Домашня» та помістіть туди HomeController, IndexViewModel, AboutViewModel тощо та всі пов'язані з цим класи, які використовуються для домашніх дій.

Якщо у вас є спільні класи, наприклад, ApplicationController, ви можете помістити його в корінь свого проекту.

Навіщо відокремлювати пов'язані між собою речі (HomeController, IndexViewModel) і зберігати разом речі, які взагалі не мають жодного відношення (HomeController, AccountController)?


Я написав допис у блозі на цю тему.


13
Якщо ви зробите це, речі швидко стануть безладними.
UpTheCreek

14
Nope, messy is to put all controllers in one dir/namespace. If you have 5 controllers, each using 5 viewmodels, then you've got 25 viewmodels. Namespaces is the mechanism for organizing code, and shouldn't be any different here.
Max Toro

41
@Max Toro: surprised you got downvoted so much. After some time working on ASP.Net MVC, I am feeling a lot of pain from having all the ViewModels in one place, all the controllers in another, and all the Views in yet another. MVC is a trio of related pieces, they are coupled - they support each other. I feel like a solution can me much more organized if the Controller, ViewModels, and Views for a given section live together in the same directory. MyApp/Accounts/Controller.cs, MyApp/Accounts/Create/ViewModel.cs, MyApp/Accounts/Create/View.cshtml, etc.
quentin-starin

13
@RyanJMcGowan розділення проблем не є розділенням класів.
Макс Торо

12
@RyanJMcGowan незалежно від того, як ви підходите до розробки, проблема полягає в тому, що ви закінчуєте, спеціально для великих програм. Опинившись в режимі обслуговування, ви не думаєте про всі моделі, а не про всі контролери, ви додаєте по одній функції одночасно.
Макс Торо

21

Я зберігаю свої програми додатків у підпапці під назвою "Core" (або окремій бібліотеці класів) і використовую ті самі методи, що і KIGG зразок програми але з деякими незначними змінами, щоб зробити мої програми більш сухими.

Я створюю клас BaseViewData в / Core / ViewData /, де зберігаю загальні властивості для всіх сайтів.

Після цього я також створюю всі свої класи ViewData в тій же папці, які потім походять від BaseViewData та мають певні властивості перегляду.

Тоді я створюю ApplicationController, з якого походять усі мої контролери. ApplicationController має загальний метод GetViewData наступним чином:

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

Нарешті, у своєму дії Controller я виконую наступне, щоб створити свою модель ViewData

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

Я думаю, що це працює дуже добре, і це тримає ваші погляди в порядку та ваших контролерів худими.


13

Клас ViewModel є там, щоб інкапсулювати декілька фрагментів даних, представлених екземплярами класів, в один простий в управлінні об'єкт, який ви можете передати у свій представлення.

It would make sense to have your ViewModel classes in their own files, in the own directory. In my projects I have a sub-folder of the Models folder called ViewModels. That's where my ViewModels (e.g. ProductViewModel.cs) live.


13

Немає хорошого місця для зберігання ваших моделей. Ви можете тримати їх в окремій збірці, якщо проект великий, і існує багато ViewModels (об’єктів передачі даних). Також ви можете зберігати їх в окремій папці проекту сайту. Наприклад, в Oxite вони розміщені в проекті Oxite, який містить також багато різних класів. Контролери в Oxite переміщені до окремого проекту, а перегляди також в окремому проекті.
У CodeCampServer ViewModels мають ім'я * Form і вони розміщуються в проекті інтерфейсу в папці Моделі.
В MvcPress проекті вони розміщуються в проекті Data, який також містить весь код для роботи з базою даних та трохи більше (але я не рекомендував такий підхід, це лише для вибірки)
Тож ви можете бачити, що існує багато точок зору. Зазвичай я зберігаю свої ViewModels (об'єкти DTO) в проекті сайту. Але коли у мене більше 10 моделей, я вважаю за краще перенести їх на окрему збірку. Зазвичай у цьому випадку я переміщую контролери для роздільної збірки.
Інше питання - як легко зіставити всі дані від моделі до вашого ViewModel. Пропоную поглянути на бібліотеку AutoMapper . Мені це дуже подобається, це все брудна робота для мене.
І я також пропоную переглянути проект SharpArchitecture . Він забезпечує дуже гарну архітектуру проектів і містить безліч крутих рамок і вказівок та велике співтовариство.


8
ViewModels! = DTO
Барт Калікто

6

ось фрагмент коду з моїх найкращих практик:

    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");
        }
}

5

Ми кидаємо всі наші ViewModels у папку Моделі (вся наша бізнес-логіка знаходиться в окремому проекті ServiceLayer)


4

Особисто я б запропонував, якщо ViewModel є не що інше, ніж тривіальне, тоді слід використовувати окремий клас.

Якщо у вас є більше однієї моделі перегляду, то я пропоную мати сенс розділити її принаймні на каталог. якщо пізніше модель перегляду поділиться, то простір імен, що міститься в каталозі, полегшує перехід до нової збірки.


2

У нашому випадку ми маємо Моделі разом з контролерами в проекті, окремому від представлень.

Як правило, ми намагалися перемістити та уникати більшості матеріалів «ViewData [» ... »] до ViewModel, таким чином ми уникаємо кастингу та магічних рядків, що добре.

ViewModel також містить деякі загальні властивості, такі як інформація про сторінки сторінки для списків або інформація заголовка сторінки, щоб малювати сухарі та заголовки. На даний момент базовий клас містить занадто багато інформації, на мою думку, і ми можемо поділити його на три частини, найпростішу та необхідну інформацію для 99% сторінок базової моделі перегляду, а потім модель для списків і модель для форм, які містять конкретні дані для цього сценарію та успадковують від базового.

Нарешті, ми впроваджуємо модель перегляду для кожної організації, яка має справу з конкретною інформацією.


0

код в контролері:

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

код у моделі перегляду:

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       
    }

проекти:

  • DevJet.Web (веб-проект ASP.NET MVC)

  • DevJet.Web.App.Dictionary (окремий проект бібліотеки класів)

    У цьому проекті я зробив декілька папок, таких як: DAL, BLL, BO, VM (папка для перегляду моделей)


Привіт, ви можете поділитися якою структурою класу Entry?
Дініс Крус

0

Створіть базовий клас перегляду, який має загально необхідні властивості, такі як результат операції та контекстні дані, ви також можете помістити поточні дані користувача та ролі

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

У базовому класі контролера є такий метод, як PopulateViewModelBase (), цей метод заповнить контекстні дані та ролі користувача. HasError та ErrorMessage встановлюють ці властивості, якщо є виняток під час витягування даних із служби / db. Прив’яжіть ці властивості до перегляду, щоб показати помилку. Ролі користувача можуть використовуватися для показу розділу приховування в режимі перегляду на основі ролей.

Для заповнення моделей перегляду в різних операціях get, це може бути узгоджено, маючи базовий контролер з абстрактним методом FillModel

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

У контролерах

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;
}
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.