在ASP.NET MVC 3中使用视图模型

17

我对视图模型还比较陌生,使用时遇到了一些问题。这里有一个情况,我想知道最佳实践是什么...

我将视图所需的所有信息都放入视图模型中。以下是一个示例,请原谅任何错误,这是我临时编写的代码。

public ActionResult Edit(int id)
{
    var project = ProjectService.GetProject(id);

    if (project == null)
        // Something about not found, possibly a redirect to 404.

    var model = new ProjectEdit();
    model.MapFrom(project); // Extension method using AutoMapper.

    return View(model);
}

如果屏幕只允许编辑一个或两个字段,那么当视图模型返回时,会缺少很多数据(这是应该的)。


(Note: I have retained the HTML tags as requested and made the text more clear and concise.)
[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
    var project = ProjectService.GetProject(id);

    if (project == null)
        // Something about not found, possibly a redirect to 404.

    try
    {
        if (!ModelState.IsValid)
            return View(model) // Won't work, view model is incomplete.

        model.MapTo(project); // Extension method using AutoMapper.
        ProjectService.UpdateProject(project);
        // Add a message for the user to temp data.

        return RedirectToAction("details", new { project.Id });
    }
    catch (Exception exception)
    {
        // Add a message for the user to temp data.

        return View(model) // Won't work, view model is incomplete.
    }
}

我的临时解决方案是从头开始重新创建视图模型,从域模型中重新填充它,再将表单数据重新应用到它上面,然后像往常一样继续进行。但这使得视图模型参数有点无意义。

[HttpPost]
public ActionResult Edit(int id, ProjectEdit model)
{
    var project = ProjectService.GetProject(id);

    if (project == null)
        // Something about not found, possibly a redirect to 404.

    // Recreate the view model from scratch.
    model = new ProjectEdit();
    model.MapFrom(project); // Extension method using AutoMapper.

    try
    {
        TryUpdateModel(model); // Reapply the form data.

        if (!ModelState.IsValid)
            return View(model) // View model is complete this time.

        model.MapTo(project); // Extension method using AutoMapper.
        ProjectService.UpdateProject(project);
        // Add a message for the user to temp data.

        return RedirectToAction("details", new { project.Id });
    }
    catch (Exception exception)
    {
        // Add a message for the user to temp data.

        return View(model) // View model is complete this time.
    }
}

有没有更优雅的方法?

编辑

两个答案都是正确的,如果可以的话我会把奖励给他们两个。然而,经过尝试,我发现 MJ 的解决方案最为简洁。

我仍然能够使用帮助程序,Jimmy。如果我将需要显示的内容添加到视图包(或视图数据)中,就像这样...

ViewBag.Project= project;

我可以执行以下操作...
@Html.LabelFor(model => ((Project)ViewData["Project"]).Name)
@Html.DisplayFor(model => ((Project)ViewData["Project"]).Name)

这是一个有些取巧的方法,有时需要在域模型上使用 System.ComponentModel.DisplayNameAttribute 进行装饰,但我已经这样做了。

我很想调用...

@Html.LabelFor(model => ViewBag.Project.Name)

但动态表达式会带来一个问题。


1
您可以在 http://prodinner.codeplex.com/ 查看 ASP.NET MVC 最佳实践(包括视图模型)。 - Omu
2个回答

13

经过一些试错(编写代码,然后讨厌它)的学习之后,我现在偏爱的方法是:

我仅使用视图模型来绑定输入字段。所以在你的情况下,如果你的视图仅编辑两个字段,则你的视图模型将只有两个属性。对于需要用于填充视图的数据(下拉列表,标签等),我使用动态的 ViewBag。

我认为显示视图(即填充视图所需的任何内容)和捕获提交的表单值(绑定、验证等)是两个独立的问题。我发现混合用于填充视图所需的数据和从视图返回的数据会变得混乱,并且往往会导致你描述的情况。我不喜欢部分填充的对象被传递。

不过,我不确定 Automapper(用于将领域对象映射到动态的 ViewBag)是否与此相符,因为我没有使用过。我相信它有一个 DynamicMap 方法可以使用?你不应该在将强类型 ViewModel 自动映射到领域对象时遇到任何问题。


1
忽略AutoMapper - 只有在您需要映射对象的问题时,显示只读视图模型才真正有帮助,如果您正在使用MVC模板化的帮助程序(Html.DisplayFor)。 - Jimmy Bogard
我喜欢这个想法,因为我的视图模型只会反映我想要的内容,并且由此变得更加简洁。我会失去像Jimmy所说的Html.DisplayFor,但是我可以通过一些自己的HtmlHelper扩展来解决这个问题。目前我并不喜欢视图模型的“税”,这种方法有减少它的优势。 - Adam Boddington

8
如果我理解正确,您的视图模型可能与您的领域实体非常相似。您提到视图模型可以返回大部分空值,因为只有某些字段是可编辑的。
假设您有一个视图,只有少数字段可供编辑(或显示),那么这些字段就是您应该在视图模型中提供的唯一字段。我通常为每个视图创建一个视图模型,然后让控制器或服务处理用户输入,并在执行某些验证后将其映射回领域实体。 这里有一个关于视图模型最佳实践的帖子,您可能会发现有用。 编辑:您还可以在Edit / POST操作中接受不同的视图模型,而不是Edit / GET操作提供的视图模型。我认为只要模型绑定器能够找到它,这应该是可行的。

这样做的额外好处是使我的视图模型更加精简。但是只显示数据放在哪里呢?您会将领域模型对象(或DTO)放入ViewData / ViewBag中来处理吗? - Adam Boddington
@Adam:我应该重新阐述我的答案。你要显示的数据也应该包含在你的视图模型中。 - Andrew Whitaker
这意味着当数据无效或异常发生时,必须重新创建 GET 视图模型(并将表单数据应用于其中,以便用户能够看到他们所更改的内容),然后再次显示视图。类似于我问题中的最后一个操作方法,但可能要等到绝对需要时才重新创建 GET 视图模型。 - Adam Boddington
@Adam:是的,没错。此外,当服务器端验证失败时,您还需要重新创建GET视图模型。不过,像AutoMapper这样的工具可以使这个过程更容易。 - Andrew Whitaker
我一直在尝试这个,安德鲁,但似乎存在安全风险。通过将所有内容放在视图模型中,包括可编辑数据和不可编辑数据,用户是否可以像非可编辑数据实际可编辑一样提交额外的值?控制器无法区分,因为它们都在视图模型中,所以会接受它。我认为在这里只有将可编辑内容放在视图模型中,并将其他所有内容放在视图包中,就像迈克尔的回答一样。你有什么想法? - Adam Boddington
显示剩余2条评论

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