如何在POST操作中将View Model映射回Domain Model?

时间:2010-02-05 08:56:55

标签: asp.net-mvc domain-driven-design viewmodel automapper separation-of-concerns

互联网上使用ViewModel并使用Automapper的每篇文章都提供了“Controller - > View”方向映射的指南。您将域模型与所有选择列表一起放入一个专门的ViewModel并将其传递给视图。这很清楚也很好 视图有一个表单,最终我们处于POST操作中。这里所有模型绑定器都与 [显然]另一个视图模型一起到达场景,该模型至少在命名约定的部分中 [显然]与原始ViewModel相关为了绑定和验证。

如何将其映射到您的域模型?

让它成为插入动作,我们可以使用相同的Automapper。但如果是更新动作怎么办?我们必须从Repository中检索我们的Domain Entity,根据ViewModel中的值更新它的属性并保存到Repository。

ADDENDUM 1(2010年2月9日):有时,分配模型的属性是不够的。应根据View Model的值对域模型采取一些措施。即,应该在域模型上调用一些方法。可能应该有一种位于Controller和Domain之间的应用服务层,以便处理View Models ...


如何整理此代码以及将其置于何处以实现以下目标?

  • 保持控制器薄
  • 荣誉SoC实践
  • 遵循域驱动设计原则
  • 是DRY
  • 继续......

4 个答案:

答案 0 :(得分:36)

我使用 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我写输入因为它更短,但这并不是很重要
希望它有所帮助

<强>更新 我现在在ProDinner ASP.net MVC Demo App中使用这种方法, 它现在被称为IMapper,还提供了一个pdf,详细解释了这种方法

答案 1 :(得分: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完成,数据访问由存储库完成。

答案 2 :(得分:5)

我想说您在客户端交互的两个方向上重复使用术语ViewModel。如果你已经阅读了足够多的ASP.NET MVC代码,你可能已经看到了ViewModel和EditModel之间的区别。我认为这很重要。

ViewModel表示呈现视图所需的所有信息。这可能包括在静态非交互式位置呈现的数据以及纯数据执行检查以确定要呈现的内容。 Controller GET操作通常负责将ViewModel打包为View。

EditModel(或者可能是ActionModel)表示执行用户想要为该POST执行的操作所需的数据。所以EditModel真的试图描述一个动作。这可能会从ViewModel中排除一些数据,虽然相关,但我认为重要的是要意识到它们确实不同。

一个想法

那说你可以很容易地使用AutoMapper配置来从Model - &gt; ViewModel和另一个来自EditModel - &gt;模型。然后不同的Controller动作只需要使用AutoMapper。地狱EditModel可以在其上有一个函数来验证它对模型的属性,并将这些值应用于模型本身。它没有做任何其他事情,你在MVC中有ModelBinder,无论如何都要将Request映射到EditModel。

另一个想法

除了我最近一直在思考的东西,就像ActionModel的想法一样,客户回馈给你的实际上是用户执行的几个动作的描述,而不仅仅是一个大的数据。这肯定需要客户端的一些Javascript来管理,但我认为这个想法很有趣。

基本上,当用户在您呈现的屏幕上执行操作时,Javascript将开始创建操作对象列表。一个例子可能是用户在员工信息屏幕上。他们更新姓氏并添加新地址,因为该员工最近已经结婚。在封面下,这会在列表中生成ChangeEmployeeNameAddEmployeeMailingAddress个对象。用户单击“保存”以提交更改,并提交两个对象的列表,每个对象仅包含执行每个操作所需的信息。

你需要一个更智能的ModelBinder然后是默认的,但是好的JSON序列化器应该能够处理客户端操作对象到服务器端操作对象的映射。服务器端(如果您处于双层环境中)可以轻松地拥有完成对其使用的模型的操作的方法。因此,Controller操作最终只是获取Model实例的Id以及要对其执行的操作列表。或者操作中包含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'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实例,以便自己执行正确的逻辑或(更有可能)使用info调用Model。

如果您处于3层环境中,IUserAction可以只是在边界上拍摄简单的DTO,并在应用层上以类似的方式执行。根据你如何做这个层,它可以很容易地分开并仍然保留在一个事务中(想到的是Agatha的请求/响应并利用DI和NHibernate的身份图)。

无论如何,我确定这不是一个完美的想法,它需要客户端的一些JS来管理,而我还没有能够做一个项目,看看它是如何展开的,但帖子试图思考关于如何到达那里然后再回来所以我想我会给出我的想法。我希望它有所帮助,我希望听到其他方法来管理互动。

答案 3 :(得分:0)

您不需要将viewmodel映射到域,因为您的viewmodel可能比域模型创建的更多。 Viewmodels针对屏幕(ui)进行了优化,与域模型不同。

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