Як зіставити модель перегляду назад із моделлю домену в дії POST?


87

Кожна стаття, знайдена в Інтернеті про використання ViewModels та використання Automapper, містить вказівки щодо відображення напрямків "Контролер -> Перегляд". Ви берете модель домену разом із усіма списками вибору в один спеціалізований ViewModel і передаєте її у подання. Це зрозуміло і добре.
Погляд має форму, і врешті-решт ми перебуваємо в дії POST. Тут всі зв’язувачі моделей виходять на сцену разом із [очевидно] іншою моделлю перегляду, яка [очевидно] пов’язана з початковим ViewModel, принаймні в тій частині конвенцій про іменування заради прив’язки та перевірки.

Як ви пов’язуєте його зі своєю моделлю домену?

Нехай це буде дія вставки, ми могли б використовувати той самий Automapper. Але що, якби це була дія оновлення? Ми маємо отримати нашу сутність домену із сховища, оновити її властивості відповідно до значень у ViewModel та зберегти у сховищі.

ДОДАТОК 1 (9 лютого 2010 р.): Іноді призначення властивостей Моделі недостатньо. Потрібно вжити певних заходів щодо Моделі Домену відповідно до значень Моделі Перегляду. Тобто, деякі методи слід викликати на моделі домену. Можливо, повинен бути якийсь рівень Служби додатків, який стоїть між контролером та доменом, щоб обробляти моделі перегляду ...


Як організувати цей код і де розмістити його для досягнення наступних цілей?

  • тримайте контролери тонкими
  • честь практики SoC
  • дотримуйтесь принципів дизайну, керованого доменом
  • бути СУХИМ
  • далі буде ...

Відповіді:


37

Я використовую інтерфейс IBuilder і реалізую його за допомогою ValueInjecter

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

... (реалізація) RebuildViewModel просто дзвонить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");
}

До речі, я не пишу ViewModel, я пишу введення, оскільки це набагато коротше, але це просто не дуже важливо
сподіваюся, це допоможе

Оновлення: я використовую цей підхід зараз у демонстраційному додатку ProDinner ASP.net MVC , зараз він називається IMapper, є також PDF-файл, де цей підхід детально пояснено


Мені подобається такий підхід. Одне, що мені незрозуміло, - це реалізація IBuilder, особливо у світлі багаторівневої програми. Наприклад, мій ViewModel має 3 списки вибору. Як реалізація конструктора отримує значення вибраного списку зі сховища?
Matt Murrell,

@Matt Murrell подивіться на prodinner.codeplex.com Я роблю це там, і я називаю це IMapper там, а не IBuilder
Ому

6
Мені подобається такий підхід, я застосував його зразок тут: gist.github.com/2379583
Пол Стовелл,

На мою думку, він не відповідає підходу доменної моделі. Це схоже на деякий CRUD-підхід для незрозумілих вимог. Чи не слід нам використовувати Factories (DDD) та відповідні методи в Моделі доменів, щоб здійснити якісь розумні дії? Таким чином, нам краще завантажити сутність із БД та оновити її за необхідності, так? Отже, схоже, це не зовсім правильно.
Артем

7

Такі інструменти, як AutoMapper, можна використовувати для оновлення існуючого об'єкта даними вихідного об'єкта. Дія контролера для оновлення може виглядати так:

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

Окрім того, що видно у фрагменті вище:

  • Дані POST для перегляду моделі + перевірка виконується в ModelBinder (може бути розширена за допомогою спеціальних прив'язок)
  • Обробка помилок (тобто вловлювання викидів доступу до даних за допомогою сховища) може здійснюватися фільтром [HandleError]

Дія контролера досить тонка, і проблеми виникають окремо: проблеми зіставлення вирішуються в конфігурації AutoMapper, перевірка виконується ModelBinder, а доступ до даних - сховищем.


6
Я не впевнений, що Automapper тут корисний, оскільки він не може змінити згладжування. Зрештою, Модель домену - це не проста DTO, як модель перегляду, тому може бути недостатньо, щоб призначити їй деякі властивості. Можливо, слід виконати деякі дії щодо Моделі домену відповідно до вмісту Моделі перегляду. Однак +1 для спільного використання непоганого підходу.
Ентоні Сердюков

@Anton ValueInjecter може змінити сплощення;)
Ому

при такому підході ви не тримаєте контролер тонким, ви порушуєте SoC і DRY ... як згадував Ому, у вас повинен бути відокремлений шар, який піклується про картографічні матеріали.
Rookian

5

Я хотів би сказати, що ви повторно використовуєте термін ViewModel для обох напрямків взаємодії з клієнтом. Якщо ви прочитали достатньо коду ASP.NET MVC у дикій природі, ви, мабуть, бачили різницю між ViewModel та EditModel. Я думаю, що це важливо.

ViewModel представляє всю інформацію, необхідну для відображення подання. Сюди можуть входити дані, які відображаються у статичних неінтерактивних місцях, а також дані виключно для здійснення перевірки, щоб вирішити, що саме відображати. Дія контролера GET, як правило, відповідає за упаковку ViewModel для її View.

EditModel (або, можливо, ActionModel) представляє дані, необхідні для виконання дії, яку користувач хотів зробити для цього POST. Тож EditModel справді намагається описати дію. Це, ймовірно, виключить деякі дані з ViewModel, і хоча пов’язані, я думаю, важливо розуміти, що вони насправді різні.

Одна ідея

Це означає, що ви можете дуже легко мати конфігурацію AutoMapper для переходу з Модель -> ViewModel та іншу, щоб перейти з EditModel -> Модель. Тоді для різних дій контролера просто потрібно використовувати AutoMapper. До біса EditModel може мати на ньому функції для перевірки його властивостей щодо моделі та застосування цих значень до самої Моделі. Це не робить нічого іншого, і у вас є ModelBinders у MVC, щоб все одно зіставити Запит із EditModel.

Ще одна ідея

Окрім того, про що я нещодавно думав, що начебто спрацьовує ідею ActionModel, це те, що клієнт до вас публікує, це насправді опис кількох дій, які виконував користувач, а не лише одна велика куля даних. Для цього, безсумнівно, потрібен певний Javascript на стороні клієнта для управління, але, на мою думку, ідея інтригує.

По суті, коли користувач виконує дії на екрані, який ви їм презентували, Javascript почне створювати список об’єктів дій. Прикладом є, можливо, користувач знаходиться на екрані інформації про працівника. Вони оновлюють прізвище та додають нову адресу, оскільки працівник нещодавно одружився. Під обкладинками це створює a ChangeEmployeeNameта AddEmployeeMailingAddressоб'єкти у списку. Користувач натискає "Зберегти", щоб здійснити зміни, і ви подаєте список двох об'єктів, кожен з яких містить лише інформацію, необхідну для виконання кожної дії.

Вам знадобиться інтелектуальніший ModelBinder, ніж стандартний, але хороший серіалізатор JSON повинен мати можливість подбати про відображення об’єктів дій на стороні клієнта до об’єктів на стороні сервера. Серверні (якщо ви перебуваєте у дворівневому середовищі) можуть легко мати методи, які завершують дію щодо Моделі, з якою вони працюють. Отже, дія Controller в кінцевому підсумку отримує лише ідентифікатор для екземпляра Model, який потрібно витягнути, і список дій, які потрібно виконати з ним. Або дії мають ідентифікатор, щоб тримати їх дуже окремими.

Тож, можливо, щось подібне реалізується на стороні сервера:

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
}

Це дійсно робить дію зворотного розміщення досить загальною, оскільки ви покладаєтесь на свій ModelBinder, щоб отримати правильний екземпляр IUserAction та ваш екземпляр IUserAction або виконувати правильну логіку самостійно, або (що, швидше за все,) викликати Модель з інформацією.

Якби ви знаходились у 3-рівневому середовищі, IUserAction можна було б просто зробити простими DTO для перестрілки через кордон і виконати подібним методом на рівні програми. Залежно від того, як ви робите цей шар, його можна дуже легко розділити і все одно залишатись у транзакції (на думку спадає запит / відповідь Агати та використання переваг карти ідентичності DI та NHibernate).

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


Цікаво. Щодо різниці між ViewModel та EditModel ... чи ви припускаєте, що для функції редагування ви б використовували ViewModel для створення форми, а потім прив'язувались до EditModel, коли користувач її опублікував? Якщо так, як би ви мали справу з ситуаціями, коли вам потрібно було б перепоставити форму через помилки перевірки (наприклад, коли ViewModel містив елементи для заповнення випадаючого списку) - чи могли б ви просто включити випадаючі елементи також у EditModel? У якому випадку, якою буде різниця між ними?
UpTheCreek,

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

0

Вам не потрібно зіставляти viewmodel з доменом, оскільки ваша viewmodel може бути створена більше, ніж модель домену. Моделі перегляду, оптимізовані для екрану (інтерфейс користувача) і відрізняються від моделі домену.

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

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