Как отобразить View Model обратно в Domain Model в действии POST? - PullRequest
85 голосов
/ 05 февраля 2010

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

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

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

ADDENDUM 1 (9 февраля 2010 г.): Иногда присваивания свойств модели недостаточно. Должны быть предприняты некоторые действия против модели домена в соответствии со значениями модели представления. Т.е. некоторые методы должны быть вызваны в доменной модели. Вероятно, должен существовать своего рода уровень службы приложений, который стоит между контроллером и доменом для обработки моделей представления ...


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

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

Ответы [ 4 ]

36 голосов
/ 05 мая 2010

Я использую интерфейс 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-файл, где этот подход подробно объясняется

7 голосов
/ 05 февраля 2010

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

5 голосов
/ 08 октября 2010

Я хотел бы сказать, что вы повторно используете термин 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 на стороне клиента, и я пока не смог сделать проект, чтобы увидеть, как он разворачивается, но пост пытался подумать о том, как добраться туда и обратно, так что я подумал, что я буду думать. Я надеюсь, что это поможет, и я хотел бы услышать о других способах управления взаимодействиями.

0 голосов
/ 29 мая 2014

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

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

...