88

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

你如何将它映射到你的领域模型?

让它成为一个插入动作,我们可以使用相同的 Automapper。但是,如果它是一个更新操作呢?我们必须从存储库中检索我们的域实体,根据 ViewModel 中的值更新它的属性并保存到存储库。

附录 1(2010 年 2 月 9 日):有时,分配模型的属性是不够的。应该根据 View Model 的值对 Domain Model 采取一些行动。即,应该在域模型上调用一些方法。可能应该有一种应用程序服务层位于控制器和域之间,以便处理视图模型......


如何组织此代码以及将其放置在何处以实现以下目标?

  • 保持控制器薄
  • 荣誉SoC实践
  • 遵循领域驱动设计原则
  • 干燥
  • 未完待续 ...
4

4 回答 4

37

I use an IBuilder interface and implement it using the ValueInjecter

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

... (implementation) RebuildViewModel just calls 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 I don't write ViewModel I write Input cuz it's much shorter, but that just not really important
hope it helps

Update: I'm using this approach now in the ProDinner ASP.net MVC Demo App, it's called IMapper now, there's also a pdf provided where this approach is explained in detail

于 2010-05-05T18:06:41.957 回答
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 完成,数据访问由 Repository 完成。

于 2010-02-05T13:06:34.297 回答
5

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

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

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

一个想法

也就是说,您可以很容易地拥有一个从 Model -> ViewModel 开始的 AutoMapper 配置,以及一个从 EditModel -> Model 开始的不同配置。那么不同的Controller动作只需要使用AutoMapper即可。该死的 EditModel 可以有一个函数来验证它对模型的属性并将这些值应用于模型本身。它没有做任何其他事情,并且您在 MVC 中有 ModelBinders 可以将 Request 映射到 EditModel 。

另一个想法

除此之外,我最近一直在考虑的事情与 ActionModel 的想法不同的是,客户端向您发送的内容实际上是对用户执行的几个操作的描述,而不仅仅是一大堆数据。这肯定需要客户端上的一些 Javascript 来管理,但我认为这个想法很有趣。

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

您将需要一个比默认的更智能的 ModelBinder,但好的 JSON 序列化程序应该能够处理客户端操作对象到服务器端操作对象的映射。服务器端的(如果您在 2 层环境中)可以轻松地拥有完成他们使用的模型上的操作的方法。因此,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 实例来执行正确的逻辑本身或(更有可能)使用信息调用模型。

如果您在 3 层环境中,则可以将 IUserAction 制作为简单的 DTO,以便跨越边界并以类似的方法在应用层上执行。根据您如何执行该层,它可以很容易地拆分并仍然保留在事务中(想到的是 Agatha 的请求/响应并利用 DI 和 NHibernate 的身份映射)。

无论如何,我确信这不是一个完美的主意,它需要客户端的一些 JS 来管理,而且我还没有能够做一个项目来看看它是如何展开的,但是这篇文章试图思考如何到那里再回来,所以我想我会给出我的想法。我希望它有所帮助,我很想听听其他管理交互的方法。

于 2010-10-08T13:42:33.360 回答
0

您不需要将视图模型映射到域,因为您的视图模型可能比域模型创建的更多。针对屏幕 (ui) 优化且不同于域模型的视图模型。

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

于 2014-05-29T15:02:20.330 回答