什么是MVC中的ViewModel?

uni*_*que 411 asp.net-mvc viewmodel

我是ASP.NET MVC的新手.我在理解ViewModel的目的时遇到了问题.

什么是ViewModel,为什么我们需要一个用于ASP.NET MVC应用程序的ViewModel?

如果我能有一个简单的例子,那就更好了.

Bre*_*ogt 590

A view model表示要在视图/页面上显示的数据,无论是用于静态文本还是用于可添加到数据库(或已编辑)的输入值(如文本框和下拉列表).这与你的不同domain model.它是视图的模型.

假设您有一个Employee代表您的员工域模型的类,它包含以下属性(唯一标识符,名字,姓氏和创建日期):

public class Employee : IEntity
{
     public int Id { get; set; }

     public string FirstName { get; set; }

     public string LastName { get; set; }

     public DateTime DateCreated { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

视图模型与域模型的不同之处在于,视图模型仅包含要在视图上使用的数据(由属性表示).例如,假设您要添加新的员工记录,您的视图模型可能如下所示:

public class CreateEmployeeViewModel
{
     public string FirstName { get; set; }

     public string LastName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,它只包含两个属性.这两个属性也在员工域模型中.你为什么这么问? Id可能未从视图中设置,它可能由Employee表自动生成.而且DateCreated还可能在存储过程或应用程序的服务层设置.因此Id,DateCreated在视图模型中不需要.当您查看员工的详细信息(已经被捕获的员工)作为静态文本时,您可能希望显示这两个属性.

加载视图/页面时,员工控制器中的create action方法将创建此视图模型的实例,如果需要,填充任何字段,然后将此视图模型传递到视图/页面:

public class EmployeeController : Controller
{
     private readonly IEmployeeService employeeService;

     public EmployeeController(IEmployeeService employeeService)
     {
          this.employeeService = employeeService;
     }

     public ActionResult Create()
     {
          CreateEmployeeViewModel model = new CreateEmployeeViewModel();

          return View(model);
     }

     public ActionResult Create(CreateEmployeeViewModel model)
     {
          // Do what ever needs to be done before adding the employee to the database
     }
}
Run Code Online (Sandbox Code Playgroud)

您的视图/页面可能如下所示(假设您正在使用ASP.NET MVCRazor视图引擎):

@model MyProject.Web.ViewModels.CreateEmployeeViewModel

<table>
     <tr>
          <td><b>First Name:</b></td>
          <td>@Html.TextBoxFor(m => m.FirstName, new { maxlength = "50", size = "50" })
              @Html.ValidationMessageFor(m => m.FirstName)
          </td>
     </tr>
     <tr>
          <td><b>Last Name:</b></td>
          <td>@Html.TextBoxFor(m => m.LastName, new { maxlength = "50", size = "50" })
              @Html.ValidationMessageFor(m => m.LastName)
          </td>
     </tr>
</table>
Run Code Online (Sandbox Code Playgroud)

因此,验证仅在FirstNameLastName.使用Fluent验证您可能会进行如下验证:

public class CreateEmployeeViewModelValidator : AbstractValidator<CreateEmployeeViewModel>
{
     public CreateEmployeeViewModelValidator()
     {
          RuleFor(m => m.FirstName)
               .NotEmpty()
               .WithMessage("First name required")
               .Length(1, 50)
               .WithMessage("First name must not be greater than 50 characters");

          RuleFor(m => m.LastName)
               .NotEmpty()
               .WithMessage("Last name required")
               .Length(1, 50)
               .WithMessage("Last name must not be greater than 50 characters");
     }
}
Run Code Online (Sandbox Code Playgroud)

使用Data Annotations,它可能看起来像这样:

public class CreateEmployeeViewModel : ViewModelBase
{
    [Display(Name = "First Name")]
    [Required(ErrorMessage = "First name required")]
    public string FirstName { get; set; }

    [Display(Name = "Last Name")]
    [Required(ErrorMessage = "Last name required")]
    public string LastName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

要记住的关键是视图模型仅表示您要使用的数据,而不是其他任何内容.如果您的域模型具有30个属性,并且您只想更新单个值,则可以想象所有不必要的代码和验证.在这种情况下,您只能在视图模型中使用这一个值/属性,而不是域对象中的所有属性.

视图模型可能不仅包含来自一个数据库表的数据.它可以组合来自另一个表的数据.以上面的例子来说明添加新的员工记录.除了只添加名字和姓氏外,您可能还想添加员工的部门.这个部门列表将来自您的Departments表格.所以现在你在一个视图模型中有来自EmployeesDepartments表的数据.然后,您需要将以下两个属性添加到视图模型中,并使用数据填充它:

public int DepartmentId { get; set; }

public IEnumerable<Department> Departments { get; set; }
Run Code Online (Sandbox Code Playgroud)

编辑员工数据(已经添加到数据库中的员工)时,与上面的示例没什么不同.创建一个视图模型,例如调用它EditEmployeeViewModel.仅包含要在此视图模型中编辑的数据,例如名字和姓氏.编辑数据,然后单击"提交"按钮.我不会太担心该Id字段,因为该Id值可能在URL中,例如:

http://www.yourwebsite.com/Employee/Edit/3
Run Code Online (Sandbox Code Playgroud)

接下来Id,将其与您的名字和姓氏值一起传递到您的存储库层.

删除记录时,我通常遵循与编辑视图模型相同的路径.我也有一个URL,例如:

http://www.yourwebsite.com/Employee/Delete/3
Run Code Online (Sandbox Code Playgroud)

当视图第一次加载时,我将使用Id3 从数据库中获取员工的数据.然后我只在我的视图/页面上显示静态文本,以便用户可以看到正在删除的员工.当用户单击"删除"按钮时,我只使用Id值3并将其传递给我的存储库层.您只需Id要从表中删除记录.

另一点,你并不需要每个动作的视图模型.如果它是简单的数据,那么只使用就可以了EmployeeViewModel.如果它是复杂的视图/页面,并且它们彼此不同,那么我建议您为每个使用单独的视图模型.

我希望这可以解决您对视图模型和领域模型的任何困惑.

  • +1提及流畅的验证 (6认同)
  • @Kenny:然后显示它:)我想说的是让我们说你有一个具有50个属性的域模型,你的视图只需要显示5然后它就没有用了发送所有50个属性只是为了显示5. (5认同)
  • @BrendanVogt - 我想也许LukLed的回答可以帮助我理解为什么这些可能有用,特别是ViewModel(可以)"......组合来自不同数据库实体的值"[我假设这个短语同样如此"数据库实体"将被替换为"模型对象"].但是,ViewModels打算解决哪些具体问题呢?你有链接吗?我自己找不到任何东西.[如果我好像在挑剔你,我道歉!] (5认同)
  • @BrendanVogt - 你做得很好解释,但我不明白"发送所有50个属性"的成本是多少.其他代码已经创建了一个具有所有50个属性的Model对象,并且似乎不值得维护另一个类而不是*不*发送45个属性 - 特别是如果你*可能*想要发送这45个属性中的任何一个未来. (4认同)
  • 对于批评我很抱歉,不幸的是,这个答案是不完整的.将视图模型定义为只需要在页面上显示的内容就像询问"什么是汽车?" 并收到一个答案"它不是一架飞机".那是真的,但不是很有帮助.VM的更正确定义是"呈现页面所需的一切".如果您读到底部,我已经确定了正确,轻松地构建VM所需的组件,在许多情况下,利用现有的域模型和表示模型. (3认同)
  • 我刚刚听到有人说 ViewModel 是将多个集合(或跨模型属性)发送到单个视图中的好方法,而不必将它们塞入 viewBag。我感觉合理。 (2认同)

Luk*_*Led 133

视图模型是表示特定视图中使用的数据模型的类.我们可以将此类用作登录页面的模型:

public class LoginPageVM
{
    [Required(ErrorMessage = "Are you really trying to login without entering username?")]
    [DisplayName("Username/e-mail")]
    public string UserName { get; set; }
    [Required(ErrorMessage = "Please enter password:)")]
    [DisplayName("Password")]
    public string Password { get; set; }
    [DisplayName("Stay logged in when browser is closed")]
    public bool RememberMe { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

使用此视图模型,您可以定义视图(Razor视图引擎):

@model CamelTrap.Models.ViewModels.LoginPageVM

@using (Html.BeginForm()) {
    @Html.EditorFor(m => m);
    <input type="submit" value="Save" class="submit" />
}
Run Code Online (Sandbox Code Playgroud)

行动:

[HttpGet]
public ActionResult LoginPage()
{
    return View();
}

[HttpPost]
public ActionResult LoginPage(LoginPageVM model)
{
    ...code to login user to application...
    return View(model);
}
Run Code Online (Sandbox Code Playgroud)

产生此结果(在提交表单后显示屏幕,带有验证消息):

如您所见,视图模型有许多角色:

  • 视图模型通过仅包含在视图中表示的字段来记录视图.
  • 视图模型可能包含使用数据注释或IDataErrorInfo的特定验证规则.
  • 查看模型定义视图应该是什么样子(为LabelFor,EditorFor,DisplayFor佣工).
  • 视图模型可以组合来自不同数据库实体的值.
  • 您可以为视图模型轻松指定显示模板,并使用DisplayFor或EditorFor帮助程序在许多地方重用它们.

视图模型及其检索的另一个例子:我们想要显示基本用户数据,他的权限和用户名.我们创建一个特殊的视图模型,它只包含必需的字段.我们从数据库中检索来自不同实体的数据,但视图只知道视图模型类:

public class UserVM {
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public bool IsAdministrator { get; set; }
    public string MothersName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

恢复:

var user = db.userRepository.GetUser(id);

var model = new UserVM() {
   ID = user.ID,
   FirstName = user.FirstName,
   LastName = user.LastName,
   IsAdministrator = user.Proviledges.IsAdministrator,
   MothersName = user.Mother.FirstName + " " + user.Mother.LastName
} 
Run Code Online (Sandbox Code Playgroud)

  • @Chandana:我相信简单的连接可以在视图模型中完成.没有理由暴露两个字段,如果它们是要一起呈现的话. (3认同)

Sam*_*Sam 77

编辑:我在我的博客上更新了这个答案:

http://www.samwheat.com/Post/The-function-of-ViewModels-in-MVC-web-development

我的答案有点冗长,但我认为将视图模型与其他类型的常用模型进行比较以了解它们为何不同以及为什么它们是必要的非常重要.

总结一下,并直接回答所提出的问题:

一般而言,视图模型是包含呈现视图所需的所有属性和方法的对象.视图模型属性通常与客户和订单等数据对象相关,此外它们还包含与页面或应用程序本身相关的属性,例如用户名,应用程序名称等.视图模型提供了一个方便的对象,可以传递给渲染引擎创建一个HTML页面.使用视图模型的众多原因之一是视图模型提供了一种单元测试某些演示任务的方法,例如处理用户输入,验证数据,检索数据以进行显示等.

以下是实体模型(a.ka.DTO的a.ka.模型),演示模型和视图模型的比较.

数据传输对象又名"模型"

数据传输对象(DTO)是一个具有与数据库中的表模式匹配的属性的类.DTO以其在数据存储中往返数据的常用用法命名.
DTO的特点:

•是业务对象 - 它们的定义取决于应用程序数据.

•通常仅包含属性 - 无代码.

•主要用于与数据库之间传输数据.

•属性与数据存储中特定表的字段完全匹配或紧密匹配.

数据库表通常被标准化,因此DTO通常也被标准化.这使得它们用于呈现数据的用途有限.但是,对于某些简单的数据结构,它们通常做得很好.

以下是DTO的两个示例:

public class Customer
{
    public int ID { get; set; }
    public string CustomerName { get; set; }
}


public class Order
{
    public int ID { get; set; }
    public int CustomerID { get; set; }
    public DateTime OrderDate { get; set; }
    public Decimal OrderAmount { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

演示模型

表示模型是一个实用程序类,用于在屏幕或报表上呈现数据.表示模型通常用于对来自多个DTO的数据组成的复杂数据结构进行建模.表示模型通常表示数据的非规范化视图.

演示模型的特点:

•是业务对象 - 它们的定义取决于应用程序数据.

•主要包含属性.代码通常仅限于格式化数据或转换为DTO或从DTO转换.Presentation Models不应包含业务逻辑.

•经常呈现非规范化的数据视图.也就是说,它们经常组合来自多个DTO的属性.

•通常包含与DTO不同的基本类型的属性.例如,美元金额可以表示为字符串,因此它们可以包含逗号和货币符号.

•通常根据它们的使用方式和对象特征来定义.换句话说,用作渲染网格的背景模型的简单DTO实际上也是该网格的上下文中的表示模型.

演示模型"根据需要"和"需要时"使用(而DTO通常与数据库模式相关联).演示模型可用于为整个页面,页面上的网格或页面上的网格下拉列表的数据建模.演示模型通常包含其他演示模型的属性.演示模型通常是为一次性目的而构建的,例如在单个页面上呈现特定网格.

示例演示模型:

public class PresentationOrder
{
    public int OrderID { get; set; }
    public DateTime OrderDate { get; set; }
    public string PrettyDate { get { return OrderDate.ToShortDateString(); } }
    public string CustomerName { get; set; }
    public Decimal OrderAmount { get; set; }
    public string PrettyAmount { get { return string.Format("{0:C}", OrderAmount); } }
}
Run Code Online (Sandbox Code Playgroud)

查看模型

视图模型类似于表示模型,因为它是用于呈现视图的后备类.然而,它与演示模型或DTO在构造方式上有很大不同.视图模型通常包含与演示模型和DTO相同的属性,因此它们通常会彼此混淆.

视图模型的特征:

•用于呈现页面或屏幕的单一数据源.通常这意味着视图模型将公开页面上任何控件需要正确呈现的每个属性.使视图模型成为视图的单一数据源,极大地提高了单元测试的能力和价值.

复合对象是否包含由应用程序数据组成的属性以及应用程序代码使用的属性.在设计可重用性的视图模型时,此特性至关重要,并在下面的示例中进行了讨论.

•包含应用程序代码.视图模型通常包含在渲染期间以及用户与页面交互时调用的方法.此代码通常涉及事件处理,动画,控件的可见性,样式等.

•包含调用业务服务的代码,以便检索数据或将数据发送到数据库服务器.此代码经常被错误地放在控制器中.从控制器调用业务服务通常会限制视图模型对单元测试的有用性.需要明确的是,视图模型本身不应包含业务逻辑,而应调用包含业务逻辑的服务.

•通常包含属性,这些属性是其他页面或屏幕的其他视图模型.

•是"每页"或"每个屏幕".通常为应用程序中的每个页面或屏幕编写唯一的视图模型.

•通常派生自基类,因为大多数页面和屏幕共享公共属性.

查看模型组成

如前所述,视图模型是复合对象,因为它们将应用程序属性和业务数据属性组合在一个对象上.在视图模型上使用的常用应用程序属性的示例如下:

•用于显示应用程序状态的属性,例如错误消息,用户名,状态等.

•用于格式化,显示,样式化或设置控件动画的属性.

•用于数据绑定的属性,例如列表对象和包含用户输入的中间数据的属性.

以下示例说明了为什么视图模型的复合特性很重要以及我们如何才能最好地构建高效且可重用的视图模型.

假设我们正在编写Web应用程序.应用程序设计的要求之一是页面标题,用户名和应用程序名称必须显示在每个页面上.如果我们想要创建一个页面来显示一个表示顺序对象,我们可以修改表示模型,如下所示:

public class PresentationOrder
{
    public string PageTitle { get; set; }
    public string UserName { get; set; }
    public string ApplicationName { get; set; }
    public int OrderID { get; set; }
    public DateTime OrderDate { get; set; }
    public string PrettyDate { get { return OrderDate.ToShortDateString(); } }
    public string CustomerName { get; set; }
    public Decimal OrderAmount { get; set; }
    public string PrettyAmount { get { return string.Format("{0:C}", OrderAmount); } }
}
Run Code Online (Sandbox Code Playgroud)

这个设计可能有效...但是如果我们想要创建一个显示订单列表的页面呢?PageTitle,UserName和ApplicationName属性将重复出现并变得难以使用.另外,如果我们想在类的构造函数中定义一些页面级逻辑,该怎么办?如果我们为将要显示的每个订单创建一个实例,我们就不能再这样做了.

继承的构成

这是一种我们可能重新考虑订单表示模型的方法,使其成为真正的视图模型,并且对于显示单个PresentationOrder对象或PresentationOrder对象的集合非常有用:

public class PresentationOrderVM
{
    // Application properties
    public string PageTitle { get; set; }
    public string UserName { get; set; }
    public string ApplicationName { get; set; }

    // Business properties
    public PresentationOrder Order { get; set; }
}


public class PresentationOrderVM
{
    // Application properties
    public string PageTitle { get; set; }
    public string UserName { get; set; }
    public string ApplicationName { get; set; }

    // Business properties
    public List<PresentationOrder> Orders { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

看看上面的两个类,我们可以看到一种思考视图模型的方法是它是一个包含另一个表示模型作为属性的表示模型.顶级表示模型(即视图模型)包含与页面或应用程序相关的属性,而表示模型(属性)包含与应用程序数据相关的属性.

我们可以进一步采用我们的设计并创建一个基本视图模型类,它不仅可以用于PresentationOrders,还可以用于任何其他类:

public class BaseViewModel
{
    // Application properties
    public string PageTitle { get; set; }
    public string UserName { get; set; }
    public string ApplicationName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以像这样简化PresentationOrderVM:

public class PresentationOrderVM : BaseViewModel
{
    // Business properties
    public PresentationOrder Order { get; set; }
}

public class PresentationOrderVM : BaseViewModel
{
    // Business properties
    public List<PresentationOrder> Orders { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我们可以通过使它成为通用的,使我们的BaseViewModel更具可重用性:

public class BaseViewModel<T>
{
    // Application properties
    public string PageTitle { get; set; }
    public string UserName { get; set; }
    public string ApplicationName { get; set; }

    // Business property
    public T BusinessObject { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

现在我们的实现毫不费力:

public class PresentationOrderVM : BaseViewModel<PresentationOrder>
{
    // done!
}

public class PresentationOrderVM : BaseViewModel<List<PresentationOrder>>
{
    // done!
}
Run Code Online (Sandbox Code Playgroud)

  • [山姆](http://stackoverflow.com/users/3230660/sam)谢谢!! 这帮助我完全掌握了一个多方面的实体:View-Model.我是一名刚刚学习MVC架构的大学生,这澄清了一系列暴露给开发人员的功能.如果可以,我会在你的答案旁边放一颗星. (2认同)
  • @AlexanderDerck它们用于不同的目的.他们彼此混淆(错误).不,您通常不会使用pres模型代替视图模型.更常见的是VM"包含"表示模型,即`MyViewModel <MyPresModel>` (2认同)
  • @Sam 假设模型对象是活动对象,例如 nhibernate 模型..所以通过使用 BusinessObject,我们不是将模型/活动对象直接暴露给视图吗?即业务对象可以直接修改数据库状态吗?另外,嵌套视图模型呢?那将需要多个业务对象属性,对吗? (2认同)

foz*_*let 22

如果您具有特定于视图的属性,并且与DB/Service/Data存储无关,则最好使用ViewModel.比如,您希望保留基于DB字段(或两个)选择的复选框,但DB字段本身不是布尔值.虽然可以在模型本身中创建这些属性并使其与数据绑定隐藏,但您可能不希望根据此类字段和事务的数量来混淆模型.

如果特定于视图的数据和/或转换太少,则可以使用模型本身


小智 19

我没有阅读所有帖子,但每个答案似乎都缺少一个真正帮助我"理解"的概念......

如果模型类似于数据库,则ViewModel类似于数据库视图 - 视图通常从一个表返回少量数据,或者从多个表(连接)返回复杂数据集.

我发现自己使用ViewModels将信息传递给视图/表单,然后在表单发回控制器时将数据传输到有效的模型中 - 对于存储列表(IEnumerable)也非常方便.


小智 11

视图模型a是一个简单的类,它可以包含多个类属性.我们使用它来继承所有必需的属性,例如我有两个类Student和Subject

Public class Student
{
public int Id {get; set;}
public string Name {get; set;}
}  
Public class Subject
{
public int SubjectID {get; set;}
public string SubjectName {get; set;}
}
Run Code Online (Sandbox Code Playgroud)

现在我们要在View中显示记录学生的姓名和主题名称(在MVC中),但是不可能添加多个类,如:

 @model ProjectName.Model.Student  
 @model ProjectName.Model.Subject
Run Code Online (Sandbox Code Playgroud)

上面的代码会抛出错误......

现在我们创建一个类并且可以给它任何名称,但是这种格式"XyzViewModel"将使它更容易理解.它是继承概念.现在我们创建一个具有以下名称的第三个类:

public class StudentViewModel:Subject
{
public int ID {get; set;}
public string Name {get; set;}
}
Run Code Online (Sandbox Code Playgroud)

现在我们在View中使用这个ViewModel

@model ProjectName.Model.StudentViewModel

现在我们可以在View中访问StudentViewModel的所有属性和继承的类.


Jer*_*oen 11

MVC没有viewmodel:它有一个模型,视图和控制器.viewmodel是MVVM(Model-View-Viewmodel)的一部分.MVVM源自表示模型,并在WPF中得到普及.在MVVM中也应该有一个模型,但大多数人完全忽略了该模式的重点,他们只有一个视图和一个视图模型.MVC中的模型类似于MVVM中的模型.

在MVC中,该过程分为3个不同的职责:

  • View负责向用户显示数据
  • 控制器负责页面流
  • 模型负责业务逻辑

MVC不太适合Web应用程序.这是Smalltalk为创建桌面应用程序而引入的模式.Web环境的行为完全不同.从桌面开发中复制一个有40年历史的概念并将其粘贴到Web环境中没有多大意义.然而,很多人认为这是可以的,因为他们的应用程序编译并返回正确的值.也就是说,在我看来,还不足以宣布某个设计选择没问题.

Web应用程序中的模型示例可以是:

public class LoginModel
{
    private readonly AuthenticationService authentication;

    public LoginModel(AuthenticationService authentication)
    {
        this.authentication = authentication;
    }

    public bool Login()
    {
        return authentication.Login(Username, Password);
    }

    public string Username { get; set; }
    public string Password { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

控制器可以像这样使用它:

public class LoginController
{
    [HttpPost]
    public ActionResult Login(LoginModel model)
    {
        bool success = model.Login();

        if (success)
        {
            return new RedirectResult("/dashboard");
        }
        else
        {
            TempData["message"] = "Invalid username and/or password";
            return new RedirectResult("/login");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您的控制器方法和模型将很小,易于测试并且非常重要.


She*_*yan 10

很多重要的例子,让我以清晰和脆弱的方式解释.

ViewModel =为视图提供服务而创建的模型.

ASP.NET MVC视图不能有多个模型,因此如果我们需要将多个模型的属性显示到视图中,则无法实现.ViewModel就是为了这个目的.

View Model是一个模型类,只能包含视图所需的属性.它还可以包含来自数据库的多个实体(表)的属性.顾名思义,此模型是根据View要求创建的.

以下几个View Models的例子

  • 要列出视图页面中多个实体的数据,我们可以创建一个View模型,并具有我们要列出数据的所有实体的属性.加入这些数据库实体并设置View模型属性并返回View以一个表格形式显示不同实体的数据
  • 视图模型可以仅定义视图所需的单个实体的特定字段.

ViewModel还可用于插入,更新记录到多个实体,但ViewModel的主要用途是将多个实体(模型)中的列显示到单个视图中.

创建ViewModel的方式与创建Model相同,为Viewmodel创建视图的方式与为Model创建视图的方式相同.

以下是使用ViewModelList数据的一个小示例.

希望这会有用.


gsi*_*nov 6

ViewModel是修补MVC框架的概念性笨拙的workarround.它代表了3层模型 - 视图 - 控制器架构中的第4层.当Model(域模型)不合适,对于View来说太大(大于2-3个字段)时,我们创建较小的ViewModel以将其传递给View.