Как отобразить View Model обратно в Domain Model в действии POST?

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

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

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

ПРИЛОЖЕНИЕ 1 (9 февраля 2010 г.): Иногда присваивания свойств модели недостаточно. Должны быть предприняты некоторые действия против модели домена в соответствии со значениями модели представления. Т.е. некоторые методы должны быть вызваны в модели предметной области. Вероятно, должен существовать своего рода уровень Application Service, который стоит между контроллером и доменом для обработки моделей представления...


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

  • держать контроллеры тонкими
  • соблюдать практику SoC
  • следовать принципам доменного дизайна
  • СУХОЙ
  • продолжение следует...

4 ответа

Я использую интерфейс 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 Я пишу Input Потому что это намного короче, но это не очень важно
Надеюсь, поможет

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

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

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

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

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

Одна идея

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

Другая идея

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

По существу, когда пользователь выполняет действия на экране, который вы им представили, Javascript начинает создавать список объектов действий. Например, возможно, пользователь находится на экране информации о сотруднике. Они обновляют фамилию и добавляют новый адрес, потому что сотрудник недавно был женат. Под одеялом это производит 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 для выполнения самой правильной логики или (что более вероятно) вызова модели с информацией.

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

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

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

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

Другие вопросы по тегам