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

互联网上关于使用 ViewModel 和 Automapper 的每篇文章都给出了“ Controller-> View”方向映射的指导方针。您将一个域模型与所有选择列表一起放入一个专门的 ViewModel 中,并将其传递给视图。很清楚,没问题。
视图有一个表单,最终我们将处于 POST 操作中。在这里,所有的模型绑定器和 [显然]另一个视图模型一起出现,[显然]另一个视图模型是原来的视图模型的 [显然]有关联,至少在为了绑定和验证的命名约定方面是这样的。

如何将它映射到域模型?

假设它是一个插入操作,我们可以使用相同的自动化程序。但如果是更新操作呢?我们必须从 Repository 中检索域实体,根据 ViewModel 中的值更新它的属性,然后保存到 Repository 中。

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


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

  • 保持控制器精简
  • 遵守社区联盟的规定
  • 遵循领域驱动设计原则
  • 干燥
  • 未完待续..。
25377 次浏览

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.

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

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.

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/