什么是MVC中的ViewModel?

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

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

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

view model表示要在视图/页面上显示的数据,无论是用于静态文本还是用于input值(如文本框和下拉列表),可以添加到数据库(或编辑)。 这是不同于你的domain model 。 这是一个观点的模型。

让我们说,你有一个Employee类,代表你的雇员域模型,它包含以下属性(唯一标识符,名字,姓氏和创builddate):

 public class Employee : IEntity { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime DateCreated { get; set; } } 

视图模型与该视图中的领域模型不同,模型只包含要在视图上使用的数据(由属性表示)。 例如,假设您想要添加新的员工logging,您的视图模型可能如下所示:

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

正如你所看到的,它只包含两个属性。 这两个属性也在雇员域模型中。 为什么你可能会问这个问题? Id可能不会从视图中设置,它可能是由Employee表自动生成的。 而DateCreated也可能在存储过程中或应用程序的服务层中设置。 所以视图模型中不需要IdDateCreated 。 当您查看员工的详细信息(已被捕获的员工)为静态文本时,您可能想要显示这两个属性。

当加载视图/页面时,您的员工控制器中的create action方法将创build此视图模型的一个实例,根据需要填充任何字段,然后将此视图模型传递给视图/页面:

 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 } } 

您的视图/页面可能如下所示(假设您正在使用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> 

因此validation只能在FirstNameLastName 。 使用Fluentvalidation,你可能有这样的validation:

 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"); } } 

而使用数据注解,它可能看起来像这样:

 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; } } 

要记住的关键是视图模型只代表你想要使用的数据 ,没有别的。 你可以想象所有不必要的代码和validation,如果你有一个30个属性的域模型,你只想更新一个值。 鉴于这种情况下,您将只在视图模型中有这个值/属性,而不是域对象中的所有属性。

视图模型可能不仅具有来自一个数据库表的数据。 它可以合并来自另一个表的数据。 以我的示例为例,添加新的员工logging。 除了添加名字和姓氏之外,您还可以添加员工的部门。 这个部门名单将来自您的Departments表。 所以现在你在一个视图模型中有来自EmployeesDepartments表的数据。 您将只需要将以下两个属性添加到您的视图模型,并用数据填充它:

 public int DepartmentId { get; set; } public IEnumerable<Department> Departments { get; set; } 

在编辑员工数据(已经添加到数据库中的员工)时,与上面的示例没有什么不同。 创build一个视图模型,例如EditEmployeeViewModel 。 在这个视图模型中只有你想要编辑的数据,比如名字和姓氏。 编辑数据并点击提交button。 我不会太在意Id域,因为Id值可能在URL中,例如:

 http://www.yourwebsite.com/Employee/Edit/3 

把这个Id并传递到您的存储库层,连同你的名字和姓氏值。

删除logging时,我通常遵循与编辑视图模型相同的path。 我也有一个URL,例如:

 http://www.yourwebsite.com/Employee/Delete/3 

当第一次加载视图时,我将使用3的Id从数据库中获取员工的数据。然后,我将在我的视图/页面上显示静态文本,以便用户可以看到要删除的员工。 当用户单击删除button时,我将只使用3的Id值,并将其传递到我的存储库层。 你只需要从表中删除一条logging就可以了。

还有一点,你并不需要每个动作的视图模型。 如果是简单的数据,那么只用EmployeeViewModel就可以了。 如果它是复杂的意见/网页,他们彼此不同,那么我build议你使用单独的视图模型。

我希望这能清除你对视图模型和领域模型的任何困惑。

视图模型是表示特定视图中使用的数据模型的类。 我们可以使用这个类作为login页面的模型:

 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; } } 

使用这个视图模型,你可以定义视图(Razor视图引擎):

 @model CamelTrap.Models.ViewModels.LoginPageVM @using (Html.BeginForm()) { @Html.EditorFor(m => m); <input type="submit" value="Save" class="submit" /> } 

和行动:

 [HttpGet] public ActionResult LoginPage() { return View(); } [HttpPost] public ActionResult LoginPage(LoginPageVM model) { ...code to login user to application... return View(model); } 

哪一个产生这个结果(屏幕提交表单后,带有validation信息):

正如你所看到的,一个视图模型有许多angular色:

  • 查看模型通过仅包含在视图中表示的字段来logging视图。
  • 视图模型可能包含使用数据注释或IDataErrorInfo的特定validation规则。
  • 视图模型定义视图的外观(对于LabelForEditorForDisplayFor帮助器)。
  • 视图模型可以组合来自不同数据库实体的值。
  • 您可以轻松地指定视图模型的显示模板,并使用DisplayFor或EditorFor帮助器在很多地方重复使用它们。

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

 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; } } 

恢复:

 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 } 

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

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

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

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

一般来说,视图模型是一个包含呈现视图所需的所有属性和方法的对象。 视图模型属性通常与数据对象(如客户和订单)相关,另外还包含与页面或应用程序本身相关的属性,例如用户名,应用程序名称等。视图模型提供了一个方便的对象,将其传递给渲染引擎创build一个html页面。 使用视图模型的许多原因之一是视图模型提供了一种unit testing某些演示任务的方法,例如处理用户input,validation数据,检索显示数据等。

以下是实体模型(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; } } 

演示模型

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

演示模型的特点:

•业务对象 – 它们的定义取决于应用程序数据。

•主要包含属性。 代码通常仅限于格式化数据或转换为DTO或从DTO转换。 演示模型不应包含业务逻辑。

•经常提出一个非规范化的数据视图。 也就是说,他们经常结合多个DTO的属性。

•通常包含与DTO不同的基本types的属性。 例如,美元金额可以表示为string,以便它们可以包含逗号和货币符号。

•通常根据使用方式以及对象特征来定义。 换句话说,一个用作渲染网格的后台模型的简单DTO实际上也是该网格上下文中的表示模型。

演示模型是“根据需要”和“需要的地方”(而DTO的通常绑定到数据库模式)使用的。 演示文稿模型可用于为整个页面,页面上的网格或页面上的网格上的下拉列表的数据build模。 表示模型通常包含属于其他表示模型的属性。 演示模型通常是为了一次性使用目的而构build的,例如在单个页面上呈现特定的网格。

示例演示模型:

 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); } } } 

查看模型

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

视图模型的特征:

•用于呈现页面或屏幕的单一数据源。 通常这意味着一个视图模型会公开每一个页面上的控件都需要正确呈现的属性。 使视图模型成为视图的单一数据源大大提高了它的unit testing的能力和价值。

•包含由应用程序数据组成的属性的复合对象以及由应用程序代码使用的属性。 在devise可重用性的视图模型时,这个特性是至关重要的,在下面的例子中讨论。

•包含应用程序代码。 视图模型通常包含在渲染过程中以及用户与页面进行交互时调用的方法。 这个代码通常涉及事件处理,animation,控件的可见性,样式等。

•包含用于检索数据或将数据发送到数据库服务器的调用业务服务的代码。 这个代码通常被错误地放在一个控制器中。 从控制器调用业务服务通常会限制视图模型在unit testing中的实用性。 要清楚的是,视图模型本身不应该包含业务逻辑,而应该调用包含业务逻辑的服务。

•通常包含其他视图模型的其他页面或屏幕的属性。

•写成“每页”或“每个屏幕”。 通常为应用程序中的每个页面或屏幕编写一个独特的视图模型。

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

查看模型组成

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

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

•用于格式化,显示,风格化或animation控制的属性。

•用于数据绑定的属性,例如列表对象和用于保存由用户input的中间数据的属性。

下面的例子说明了为什么视图模型的复合性质是重要的,以及我们如何才能最好地构build一个高效且可重用的视图模型。

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

 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); } } } 

这种devise可能会工作…但是如果我们想创build一个页面来显示订单列表呢? PageTitle,UserName和ApplicationName属性将重复使用,并变得笨拙。 另外,如果我们想在类的构造函数中定义一些页面级的逻辑呢? 如果我们为每个将要显示的订单创build一个实例,我们不能再这样做。

组成inheritance

以下是我们可能重新构build订单表示模型的一种方式,使其成为真实的视图模型,并且对于显示单个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; } } 

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

我们可以将我们的devise更进一步,创build一个基本视图模型类,不仅可以用于PresentationOrders,也可以用于其他任何类。

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

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

 public class PresentationOrderVM : BaseViewModel { // Business properties public PresentationOrder Order { get; set; } } public class PresentationOrderVM : BaseViewModel { // Business properties public List<PresentationOrder> Orders { get; set; } } 

我们可以通过使其通用来使我们的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; } } 

现在我们的实现很容易:

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

如果您具有特定于视图的属性,而与DB / Service / Data存储无关,则最好使用ViewModels。 说,你想留下一个基于一个数据库字段(或两个)select的checkbox,但数据库字段本身不是一个布尔值。 尽pipe可以在模型本身中创build这些属性,并使其不受数据绑定的影响,但您可能不希望根据这些字段和事务的数量来混淆模型。

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

我没有阅读所有的post,但每个答案似乎都缺less一个概念,真正帮助我“得到它”…

如果Model类似于数据库Table ,那么ViewModel类似于数据库View – 视图通常要么从一个表中返回less量的数据,要么从多个表(连接)中返回复杂的数据集。

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

MVC没有viewmodel:它有一个模型,视图和控制器。 视图模型是MVVM(Model-View-Viewmodel)的一部分。 MVVM从演示模型派生,并在WPF中推广。 在MVVM中也应该有一个模型,但是大多数人完全忽略了这个模式的观点,他们只会有一个观点和一个观点模型。 MVC中的模型与MVVM中的模型相似。

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

  • View负责将数据呈现给用户
  • 一个控制器负责页面stream
  • 模型负责业务逻辑

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

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; } } 

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

 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"); } } } 

您的控制器方法和您的模型将会很小,易于testing,并且重点突出。

查看模型a是可以包含多个类属性的简单类。 我们使用它来inheritance所有必需的属性,例如我有两个类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;} } 

现在我们要在视图(MVC)中显示logging学生的姓名和主题的名字,但不可能添加多个类,如:

  @model ProjectName.Model.Student @model ProjectName.Model.Subject 

上面的代码会抛出一个错误…

现在我们创build一个类,并可以给它任何名称,但是这种格式“XyzViewModel”将使它更容易理解。 这是inheritance的概念。 现在我们创build第三个类,名字如下:

 public class StudentViewModel:Subject { public int ID {get; set;} public string Name {get; set;} } 

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

@model ProjectName.Model.StudentViewModel

现在我们可以访问View中的所有StudentViewModel属性和inheritance类。

很多大的例子,让我以清晰而脆弱的方式解释。

ViewModel =为创build视图而创build的模型。

ASP.NET MVC视图不能有多个模型,所以如果我们需要将多个模型的属性显示到视图中,这是不可能的。 ViewModel服务于此目的。

视图模型是一个模型类,只能保存视图所需的属性。 它也可以包含来自数据库的多个实体(表)的属性。 顾名思义,这个模型是根据View的需求创build的。

以下几个视图模型的例子

  • 要从视图页面中的多个实体中列出数据,我们可以创build一个View模型,并拥有我们想要列出数据的所有实体的属性。 join这些数据库实体并设置视图模型属性并返回到视图以一种表格forms显示不同实体的数据
  • 视图模型可以仅定义视图所需的单个实体的特定字段。

ViewModel也可以用来插入,更新logging到多个实体,但是ViewModel的主要用途是将多个实体(模型)的列显示到一个视图中。

创buildViewModel的方式与创buildModel相同,为Viewmodel创build视图的方式与为Model创build视图的方式相同。

这是使用ViewModel列表数据的一个小例子。

希望这会有用。

ViewModel是修补了MVC框架的概念笨拙的workarround。 它代表了三层模型 – 视图 – 控制器体系结构中的第四层。 当模型(领域模型)不适合,视图太大(大于2-3个领域),我们创build较小的ViewModel传递给视图。