在MVC中优雅地使用ViewModels进行POST操作

23

目前,我将我的领域对象传递给视图,并直接从POST进行绑定。每个人都说这样做不好,因此我正在尝试添加ViewModel的概念。

但是,我找不到一种非常优雅的方法来实现这一点,我想知道其他人解决这个问题的解决方案,以避免最终出现非常混乱的控制器操作。

例如,“添加人员”功能的典型过程如下:

  1. 获取表示空白Person ViewModel的视图的GET请求
  2. 提交(无效)数据
  3. 控制器将发布的数据绑定到person viewmodel上
  4. 如果绑定失败,则需要执行与(1)相同的操作,但具有一些数据,而不是空对象和错误
  5. 如果绑定成功,则需要将VM属性映射到一个真实的模型上
  6. 验证模型
  7. 如果验证通过:保存person、提交、将用户详细信息映射到显示VM并在视图中返回它
  8. 如果验证失败,则需执行与(1)相同的操作,但具有一些数据和错误

在控制器操作中执行所有这些操作(忽略GET)肯定不符合SRP或DRY。

我正在尝试想出一种方法,以便将此过程拆分为模块化的、干净的方法,并且最重要的是可测试。

人们对这个问题的解决方案是什么?

我一直在尝试使用自定义控制器操作调用程序将关注点分离成单独的方法、智能模型绑定器和简单粗暴的方法,但我还没有找到一个满意的解决方案。

P.S. 由于它增加了很多复杂性,请说服我为什么要费心


1
还没有决定一个优雅的解决方案,以免我的控制器变得非常混乱。我认为真正的答案是OpenRasta。 - Andrew Bullock
也许这些帖子可以有所帮助:http://stackoverflow.com/a/25460769/3969501,http://stackoverflow.com/a/25169023/1475331 - turdus-merula
3个回答

6
我也感到了同样的不适。我的解决方法只有以下几点:
  1. 创建一个绑定器来绑定和验证视图模型
  2. 创建一个绑定器来从数据库中获取实体(或者在控制器中完成此操作)
  3. 调用超类中继承的Save方法。该方法接受视图模型和将要更新的实体,执行您在步骤中列出的所有工作。
动作方法如下:
public ActionResult Whatever(TViewModel viewModel, TEntity entity)
{
    return Save(viewModel, entity);
}

基础控制器有一个通用的定义,如下所示:
public abstract BaseController<TEntity, TViewModel>
    where TEntity : Entity
    where TViewModel : ViewModel

构造函数有两个依赖项,一个是实体仓库,另一个是模型映射器,如下所示:
protected BaseController(IRepository<TEntity> repository, IMapper<TEntity, TViewModel> mapper)

有了这个方法,你就可以编写一个受保护的Save方法,并可以从子类的控制器操作中调用,如下所示:

protected ActionResult Save(TViewModel viewModel, TEntity entity)
{
    if (!ModelState.IsValid)
        return View(viewModel);

    _mapper.Map(viewModel, entity);
    if (!entity.IsValid)
    {
        // add errors to model state
        return View(viewModel);
    }

    try
    {
        _repository.Save(entity);
        // either redirect with static url or add virtual method for defining redirect in subclass.
    }
    catch (Exception)
    {
        // do something here with the exception
        return View(viewModel);
    }
}

就测试性而言,您可以测试保存方法,传入有效/无效的视图模型和实体。您可以分别测试模型映射器的实现、视图模型的有效状态和实体的有效状态。

通过使基础控制器成为通用控制器,如果您正在创建多个控制器执行相同的操作,您可以为每个实体/视图模型组合重复此模式。

我非常想听听其他人对此有什么看法。很好的问题。


2
绝对是有趣的想法,但OpenRASTA仍然是更好的选择 :) - David

2

MVVM(ViewModel)模式绝对是最好的选择,我之前有一个类似的问题,关于将数据POST回到某个操作中 - 这是链接:MVVM和ModelBinders在ASP.NET MVC框架中

结果是您可以使用Bind属性将所需的复杂类型POST回来。


0

我在 asp.net mvc 示例应用程序 中有许多好的解决方案,它们都在 valueinjecter 的下载链接 中(这是我用来将 ViewModels 映射到/从 Entities 的映射器,您还可以将 FormCollection/Request 映射到 Entities)

以下是其中一个:

    public class TinyController :Controller
        {
            private readonly IModelBuilder<Person, PersonViewModel> modelBuilder;

            public TinyController()
            {
                modelBuilder = new PersonModelBuilder();
            }

            public ActionResult Index()
            {
                return View(modelBuilder.BuildModel(new PersonRepository().Get()));
            }

            [HttpPost]
            public ActionResult Index(PersonViewModel model)
            {
                if (!ModelState.IsValid)
                    return View(modelBuilder.RebuildModel(model));

                   var entity = modelBuilder.BuildEntity(model);
...
//save it or whatever
            }
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接