ASP.NET MVC - 如何准确使用视图模型

37

假设我有一个页面允许编辑用户的详细信息,所以我有一个像这样的ViewModel:

public class UserViewModel {
    public string Username { get; set; }
    public string Password { get; set; }
    public int ManagerId { get; set; }
    public string Category { get; set; }
}

因此,在我的EditUser操作中,我可以通过模型绑定器将此传递回来,然后我可以将其映射到域模型:

public ActionResult EditUser(UserViewModel user) {
    ...

然而,显示表单的页面还需要详细信息,例如管理员和类别列表,以为这些字段提供下拉框。它可能还会在侧边栏中显示其他用户列表,以便您可以在不同用户之间切换编辑。

因此,我又有了另一个视图模型:

public class ViewUserViewModel {
    public UserViewModel EditingUser { get; set; }
    public IEnumerable<SelectListItem> Managers { get; set; }
    public IEnumerable<SelectListItem> Categories { get; set; }
    public IEnumerable<SelectListItem> AllUsers { get; set; }
}

这样做是正确的吗?它们都是视图模型吗?如果是,我应该使用什么命名约定来区分类似于模型的VM和仅包含页面数据的VM?

我理解错了吗?

3个回答

95
“视图模型”只是一种模式,这个名称并没有什么神奇的地方,但通常任何传递给视图的类(无论是仅用于显示数据还是用于表单提交)都被称为“视图模型”,并赋予类似FooViewModelFooVM 这样的名称表示它是该“视图模型”模式的一部分。
我不想在这里过多阐述关于所使用的模式,但我认为稍微提及一下会有所帮助。显然,ASP.NET MVC鼓励MVC(Model-View-Controller)体系结构模型。在MVC中,“Model”是所有应用程序的业务逻辑容器。“Controller”负责处理请求,获取模型,在视图中使用该模型呈现界面,并返回响应。这看起来任务重大,但实际上框架在幕后处理了大部分工作,因此控制器通常(也应该)只需要很少量的代码。它们负责最少量的功能以连接所有内容。最后,“View”负责创建UI层,允许用户与模型中的数据进行交互。它不负责数据本身,也不应该(ViewData / ViewBag在这里是一个相当大的违规,至少在开发人员实践中的使用方式如此)。
因此,这意味着你的应用程序逻辑的大部分应该在模型中,通常情况下这是个好事。但是,由于模型是应用程序数据的中心,它通常会被存储在数据库或类似的地方。这就产生了一些利益冲突,因为现在您需要在持久化数据和仅存在于显示目的上做出平衡。

这就是视图模型发挥作用的地方。MVVM(Model-View-ViewModel),与MVC有些相似,意识到了一种一模式来掌控所有问题的固有问题。我不会在此处详细介绍,因为MVC并没有使用此模式。然而,大多数ASP.NET MVC开发人员已经采用了MVVM的视图模型(View Model)。您最终得到的基本上是一个数据库支持的实体(传统模型),然后通常有许多不同的视图模型来表示该实体的各个状态。这使您的模型可以包含与持久性相关的业务逻辑,而视图模型则包含与显示、创建和更新该模型相关的业务逻辑。

我有点偏离主题了,但长话短说,您所做的事情是完全可以接受的。事实上,这是一个好的实践。根据应用程序的要求创建尽可能多的视图模型,并使用它们来存储实际需要的数据和业务逻辑。 (其中包括像SelectList这样的东西。您的控制器或视图都不应该知道如何为下拉列表框创建SelectList。)


6
这个主题的最佳解释。你应该把它放在博客上!+1 - Rafael A. M. S.
您提到模型应该负责应用程序的业务逻辑。通过“业务”,您可能指的是所有数据准备、查询、过滤、将一个模型投影到另一个模型或特定的ViewModel。然后,控制器将这样准备好的ViewModel传递给View。您如何实现它?您如何设计模型以执行业务?例如,您是否将所有控制器方法移动到表示视图模型的类中?目前,我的控制器中有很多函数和“业务”,它们完成所有细节。谢谢。 - Celdor
坦白地说,在ASP.NET MVC中你真的做不到。为了真正理解我在说什么,建立一个Ruby on Rails的示例应用程序。RoR非常严格地遵循MVC模式,你将看到他们的模型需要多少工作量。另一方面,ASP.NET MVC只是松散地遵循MVC。你的“Model”将是实体类、视图模型和类似于存储库或服务的组合。你应该尝试保持你的控制器薄,但你不能把所有逻辑都移到一个类中。 - Chris Pratt

20

如何使用快捷方式完成这个过程:

  1. 为页面上的每个表单创建一个单独的ViewModel类,然后使用PartialViews呈现这些类:@{Html.RenderPartial("PartialName", Model.PartialModel);}
  2. 如果页面包含诸如html metas之类的内容,则创建一个单独的meta类并将其放在页面的section中。
  3. 其他情况,例如“我应该将这个放在单独的类中吗?”则需要凭借您的判断。

例如,您有一个页面,其中包含某种登录/注册栏或弹出窗口。

public class SomePageViewModel
{
    public RegisterBarVM Register { get; set; }
    public LoginBarVM LoginBar { get; set; }

    public MetasVM Metas { get; set; }
    public string MaybePageTitle { get; set;}
    public string MaybePageContent { get; set;}

    [HiddenInput(DisplayValue = false)]
    public int IdIfNeeded { get; set; }

    public IEnumerable<SelectListItem> SomeItems {get; set;}
    public string PickedItemId { get;set; }
}

public class RegisterBarVM
{
    public string RegisterUsername {get;set;}
    public string RegisterPassword {get;set;}
    //...
}

public class LoginBarVM
{
    public string LoginUserame {get;set;}
    public string LoginPassword {get;set;}
    //...
}

//cshtml
@model yourClassesNamespace.SomePageViewModel
@{
    Html.RenderPartial("LoginBar", Model.LoginBar); //form inside
    Html.RenderPartial("RegisterBar", Model.RegisterBar); //form inside

    using(Html.BeginForm())
    {
        @Html.EditorFor(m => m.IdIfNeeded)
        @Hmtl.EditorFor(m => m.MaybePageTitle)
        @Hmtl.EditorFor(m => m.MaybePageContent)

        @Hmtl.DropDownListFor(m => m.PickedItemId, new SelectList(Model.SomeItems))

        <input type="submit" value="Update" />
    }
}

@section Metas {
    @{Html.RenderPartial("Meatas", Model.Metas}
}

关于编辑器模板,可以参考Brad Wilson的博客,或者通过谷歌搜索或查找堆栈资源了解有关显示/编辑模板和HtmlHelpers的信息。它们都非常有用,可以帮助您构建一致的网站。


9
我个人更喜欢将页面渲染所需的所有信息都放在ViewModel中,因为这就是ViewModel的目的——为视图提供所有数据。因此,我的将包含、和属性,控制器将在将ViewModel传递给视图之前填充这些集合。
这本质上就是你所做的——它只是从方程式中删除了额外的ViewModel。
我也看到其他程序员使用ViewData将下拉列表发送到视图,但我不喜欢这样做,因为ViewData没有强类型,而ViewModel是有强类型的。

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