Как сопоставить View Model с моделью домена в действии POST?

Каждая статья, найденная в Интернете по использованию ViewModels и использующая Automapper, дает рекомендации по направлению "Controller → View". Вы берете модель домена вместе со всеми Select Lists в один специализированный ViewModel и передаете его в представление. Это ясно и прекрасно.
Представление имеет форму, и в конечном итоге мы находимся в действии POST. Здесь все связки модели выходят на сцену вместе с [очевидно] другой View Model, которая [очевидно] связана с исходным ViewModel, по крайней мере, в части соглашений об именах для привязки и проверки.

Как вы сопоставляете его с моделью домена?

Пусть это будет действие вставки, мы могли бы использовать тот же Automapper. Но что, если это было актом обновления? Мы должны получить нашу доменную сущность из репозитория, обновить ее свойства в соответствии со значениями в ViewModel и сохранить в репозитории.

ДОБАВЛЕНИЕ 1 (9 февраля 2010 г.): Иногда присвоение свойств модели недостаточно. Должны быть предприняты некоторые действия против модели домена в соответствии со значениями View Model. I.e., некоторые методы следует вызывать в доменной модели. Вероятно, должен быть какой-то слой Application Service, который стоит между контроллером и доменом, чтобы обрабатывать View Models...


Как организовать этот код и где его разместить для достижения следующих целей?

  • держать контроллеры в тонком
  • честь Практика SoC
  • следовать принципам проектирования, основанным на доменах.
  • быть сухим
  • Продолжение следует...

Ответ 1

Я использую интерфейс 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");
}

btw Я не пишу ViewModel Я пишу Input, потому что он намного короче, но это просто не очень важно
надеюсь, что это поможет

Update: Я использую этот подход сейчас в ProDinner ASP.net MVC Demo App, он теперь называется IMapper, там также представлен pdf-документ, в котором этот подход подробно объясняется

Ответ 2

Инструменты, такие как 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 и доступа к данным через репозиторий.

Ответ 3

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

ViewModel представляет всю информацию, необходимую для визуализации представления. Это может включать данные, которые отображаются в статических неинтерактивных местах, а также данные, предназначенные исключительно для проверки, чтобы определить, что именно сделать. Действия контроллера GET обычно отвечают за упаковку ViewModel для его просмотра.

EditModel (или, возможно, ActionModel) представляет данные, необходимые для выполнения действий, которые пользователь хотел сделать для этого POST. Поэтому EditModel действительно пытается описать действие. Вероятно, это исключает некоторые данные из ViewModel, и хотя я считаю, что важно понять, что они действительно разные.

Одна идея

Тем не менее, вы можете легко настроить AutoMapper для перехода с Model → ViewModel, а другой - с EditModel → Model. Тогда для разных действий контроллера нужно просто использовать AutoMapper. Черт, EditModel может иметь на нем функции для проверки его свойств против модели и применения этих значений к самой модели. Это не делает ничего другого, и у вас есть ModelBinders в MVC, чтобы в любом случае сопоставить Request to EditModel.

Другая идея

Помимо того, о чем-то, о чем я думал недавно, это то, что работает с идеей ActionModel, заключается в том, что то, что клиент отправляет вам обратно, на самом деле представляет собой описание нескольких действий, выполненных пользователем, а не только одного большого глобуса данных, Для этого, конечно, потребуется некоторый Javascript на стороне клиента, но идея интригует, я думаю.

По сути, поскольку пользователь выполняет действия на экране, который вы им представили, Javascript начнет создавать список объектов действий. Например, пользователь может находиться на экране информации о сотрудниках. Они обновляют фамилию и добавляют новый адрес, потому что недавно сотрудник был женат. Под обложками создается список ChangeEmployeeName и AddEmployeeMailingAddress. Пользователь нажимает "Сохранить", чтобы зафиксировать изменения, и вы отправляете список из двух объектов, каждый из которых содержит только информацию, необходимую для выполнения каждого действия.

Вам понадобится более интеллектуальный ModelBinder, чем стандартный, но хороший сериализатор JSON должен уметь следить за отображением объектов действия стороны клиента на серверные. На стороне сервера (если вы находитесь в среде с двумя уровнями) могут легко быть методы, которые завершили действие с моделью, с которой они работают. Таким образом, действие Controller заканчивается тем, что просто получает идентификатор Id для экземпляра модели и список действий для его выполнения. Или действия имеют идентификатор в них, чтобы держать их в отдельности.

Итак, что-то вроде этого реализуется на стороне сервера:

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

Это действительно делает действие posting back довольно общим, так как вы полагаетесь на свой ModelBinder, чтобы получить правильный экземпляр IUserAction и ваш экземпляр IUserAction, чтобы либо выполнить правильную логику, либо (более вероятно) вызвать модель с информацией.

Если бы вы были в трехуровневой среде, IUserAction можно было просто сделать простыми DTO, которые будут расстреляны по границе и выполняться аналогичным методом на уровне приложения. В зависимости от того, как вы делаете этот слой, его можно легко разделить и по-прежнему оставаться в транзакции (что приходит в голову - это запрос/ответ Агаты и использование карты идентификации DI и NHibernate).

В любом случае, я уверен, что это не идеальная идея, для этого потребуется некоторое JS на стороне клиента, и я еще не смог выполнить проект, чтобы увидеть, как он разворачивается, но почта пыталась подумать о том, как добраться туда и обратно, поэтому я решил, что буду давать свои мысли. Я надеюсь, что это поможет, и я хотел бы услышать о других способах управления взаимодействиями.

Ответ 4

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

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