如何在 POST 操作中将视图模型映射回域模型?

发布于 2024-08-20 00:39:55 字数 669 浏览 8 评论 0原文

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

如何将它映射到域模型?

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

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


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

  • 保持控制器纤薄,
  • 尊重 SoC 实践,
  • 遵循领域驱动设计原则,
  • 保持 DRY
  • 待续……

Every article found in the Internet on using ViewModels and utilizing Automapper gives the guidelines of the "Controller -> View" direction mapping. You take a domain model along with all Select Lists into one specialized ViewModel and pass it to the view. That's clear and fine.
The view has a form, and eventually we are in the POST action. Here all the Model Binders come to the scene along with [obviously] another View Model which is [obviously] related to the original ViewModel at least in the part of naming conventions for the sake of binding and validation.

How do you map it to your Domain Model?

Let it be an insert action, we could use the same Automapper. But what if it was an update action? We have to retrieve our Domain Entity from the Repository, update it's properties according to the values in the ViewModel and save to the Repository.

ADDENDUM 1 (9th of February 2010): Sometimes, assigning Model's properties is not enough. There should be taken some action against Domain Model according to the values of View Model. I.e., some methods should be called on Domain Model. Probably, there should be a kind of an Application Service layer which stands between Controller and Domain in order to process View Models...


How to organize this code and where to place it to achieve the following goals?

  • keep controllers thin
  • honor SoC practice
  • follow Domain-Driven Design principles
  • be DRY
  • to be continued ...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

相思碎 2024-08-27 00:39:55

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

更新:
我现在在 ProDinner ASP.net MVC 演示应用中使用这种方法,
现在它被称为 IMapper,还提供了一个 pdf 文件,其中详细解释了这种方法

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

战皆罪 2024-08-27 00:39:55

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

除了上面代码片段中可见的内容之外:

  • 将数据发布到视图模型 + 验证在 ModelBinder 中完成(可以使用自定义绑定进行扩展)
  • 错误处理(即捕获存储库抛出的数据访问异常)可以通过 [HandleError] 过滤器来完成

控制器操作非常薄,并且关注点是分开的:映射问题在 AutoMapper 配置中解决,验证由 ModelBinder 完成,数据访问由 Repository 完成。

Tools like AutoMapper can be used to update existing object with data from source object. The controller action for updating might look like:

[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);
}

Apart from what is visible in the snippet above:

  • POST data to view model + validation is done in ModelBinder (could be exended with custom bindings)
  • Error handling (i.e. catching data access exception throws by Repository) can be done by [HandleError] filter

Controller action is pretty thin and concerns are separated: mapping issues are addressed in AutoMapper configuration, validation is done by ModelBinder and data access by Repository.

︶葆Ⅱㄣ 2024-08-27 00:39:55

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

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

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

一个想法

这就是说,您可以很容易地拥有一个 AutoMapper 配置,用于从模型 -> ViewModel 和 EditModel 中的另一个不同 ->模型。那么不同的Controller动作只需要使用AutoMapper就可以了。 EditModel 上可以有一个函数来根据模型验证其属性并将这些值应用到模型本身。它没有做任何其他事情,并且 MVC 中的 ModelBinder 可以将请求映射到 EditModel。

另一个想法

除此之外,我最近一直在思考的事情与 ActionModel 的想法类似,即客户端发回给您的内容实际上是用户执行的几个操作的描述,而不是只是一大堆数据。这肯定需要客户端使用一些 JavaScript 来管理,但我认为这个想法很有趣。

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

您将需要一个比默认模型更智能的 ModelBinder,但良好的 JSON 序列化程序应该能够处理客户端操作对象到服务器端操作对象的映射。服务器端(如果您处于 2 层环境中)可以轻松地拥有在其使用的模型上完成操作的方法。因此,控制器操作最终只是获取要拉取的模型实例的 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 来管理,而且我还没有能够做一个项目来看看它是如何展开的,但这篇文章试图考虑如何去那里然后再回来,所以我想我会表达我的想法。我希望它有所帮助,并且我很乐意听到其他管理交互的方法。

I would like to say that you reuse the term ViewModel for both directions of the client interaction. If you have read enough ASP.NET MVC code in the wild you have probably seen the distinction between a ViewModel and an EditModel. I think that is important.

A ViewModel represents all the information required to render a view. This could include data that is rendered in static non-interactive places and also data purely to perform a check to decide on what exactly to render. A Controller GET action is generally responsible for packaging up the ViewModel for its View.

An EditModel (or perhaps an ActionModel) represents the data required to perform the action the user wanted to do for that POST. So an EditModel is really trying to describe an action. This will probably exclude some data from the ViewModel and although related I think it's important to realize they are indeed different.

One Idea

That said you could very easily have an AutoMapper configuration for going from Model -> ViewModel and a different one to go from EditModel -> Model. Then the different Controller actions just need to use AutoMapper. Hell the EditModel could have a functions on it to validate it's properties against the model and to apply those values to the Model itself. It's not doing anything else and you have ModelBinders in MVC to map the Request to the EditModel anyway.

Another Idea

Beyond that something I have been thinking about recently that sort of works off the idea of an ActionModel is that what the client is posting back to you is actually the description of several actions the user performed and not just one big glob of data. This would certainly require some Javascript on the client side to manage but the idea is intriguing I think.

Essentially as the user performs actions on the screen you have presented them, Javascript would start create a list of action objects. An example is possibly the user is at an employee information screen. They update the last name and add a new address because the employee has recently been married. Under the covers this produces a ChangeEmployeeName and an AddEmployeeMailingAddress objects to a list. The user clicks 'Save' to commit the changes and you submit the list of two objects, each containing just the information needed to perform each action.

You would need a more intelligent ModelBinder then the default one but good JSON serializer should be able to take care of the mapping of the client side action objects to the server side ones. The server side ones (if you are in a 2-tier environment) could easily have methods that completed the action on the Model they work with. So the Controller action ends up just getting an Id for the Model instance to pull and a list of actions to perform on it. Or the actions have the id in them to keep them very separate.

So maybe something like this gets realized on the server side:

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
}

That really makes the posting back action fairly generic since you are relying on your ModelBinder to get you the correct IUserAction instance and your IUserAction instance to either perform the correct logic itself or (more likely) call into the Model with the info.

If you were in a 3 tier environment the IUserAction could just be made simple DTOs to be shot across the boundary and performed in a similar method on the app layer. Depending on how you do that layer it could be split up very easily and still remain in a transaction (what comes to mind is Agatha's request/response and taking advantage of DI and NHibernate's identity map).

Anyway I'm sure it's not a perfect idea, it would require some JS on client side to manage, and I haven't been able to do a project yet to see how it unfolds, but the post was trying to think about how to get there and back again so I figured I would give my thoughts. I hope it helps and I would love to hear of other ways to manage the interactions.

拧巴小姐 2024-08-27 00:39:55

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

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

You don't need mapping viewmodel to domain because your viewmodel may be created more than domain model. Viewmodels optimized for screen (ui) and different from domain model.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文