什么是MVC中的ViewModel?
我是ASP.NET MVC
新手。 我在理解ViewModel
的目的方面存在问题。
什么是ViewModel
,为什么我们需要一个ASP.NET MVC
应用程序的ViewModel
?
如果我能有一个简单的例子,那会更好。
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; }
}
视图模型与该视图中的领域模型不同,模型仅包含要在视图上使用的数据(由属性表示)。 例如,假设您想要添加新的员工记录,您的视图模型可能如下所示:
public class CreateEmployeeViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
正如你所看到的,它只包含两个属性。 这两个属性也在员工域模型中。 为什么你可能会问这个问题? 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
}
}
您的视图/页面可能如下所示(假设您正在使用ASP.NET MVC
和Razor
视图引擎):
@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>
因此验证只能在FirstName
和LastName
。 使用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");
}
}
对于数据注释,它可能看起来像这样:
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; }
}
关键要记住的是,视图模型仅代表您想要使用的数据 ,没有别的。 如果您有一个具有30个属性的域模型,并且您只想更新单个值,则可以想象所有不必要的代码和验证。 鉴于这种情况,您只能在视图模型中拥有这一个值/属性,而不是域对象中的所有属性。
视图模型可能不仅具有来自一个数据库表的数据。 它可以组合另一个表中的数据。 以我的示例为例,添加新的员工记录。 除了只添加姓和名,您可能还想添加员工的部门。 这个部门清单将来自您的Departments
表格。 所以现在你在一个视图模型中有来自Employees
和Departments
表的数据。 您将需要将以下两个属性添加到您的视图模型并使用数据填充它:
public int DepartmentId { get; set; }
public IEnumerable<Department> Departments { get; set; }
在编辑员工数据(已经添加到数据库中的员工)时,与上面的示例没有什么不同。 创建一个视图模型,将其称为EditEmployeeViewModel
。 只有你想在这个视图模型中编辑的数据,比如名字和姓氏。 编辑数据并点击提交按钮。 我不会太担心Id
字段,因为Id
值可能会出现在URL中,例如:
http://www.yourwebsite.com/Employee/Edit/3
取这个Id
并将其传递到您的存储库层,以及您的名字和姓氏值。
删除记录时,我通常遵循与编辑视图模型相同的路径。 我也会有一个URL,例如:
http://www.yourwebsite.com/Employee/Delete/3
第一次加载视图时,我将使用3的Id
从数据库中获取员工的数据。然后,我会在我的视图/页面上显示静态文本,以便用户可以看到要删除的员工。 当用户单击“删除”按钮时,我将使用3的Id
值并将其传递到我的存储库层。 你只需要Id
从表中删除一条记录。
还有一点,你并不需要每个动作的视图模型。 如果它是简单的数据,那么只使用EmployeeViewModel
会很好。 如果它是复杂的意见/页面,并且它们彼此不同,那么我建议你为每个视图使用单独的视图模型。
我希望这可以消除你对视图模型和领域模型的任何困惑。
视图模型是表示特定视图中使用的数据模型的类。 我们可以使用这个类作为登录页面的模型:
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);
}
这产生了这个结果(屏幕在提交表单后带有验证信息):
如您所见,视图模型具有许多角色:
LabelFor
, EditorFor
, DisplayFor
助手)。 视图模型及其检索的另一个示例:我们要显示基本的用户数据,他的权限和用户名。 我们创建一个特殊的视图模型,其中只包含必需的字段。 我们从数据库中检索不同实体的数据,但是视图只知道视图模型类:
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
我的答案有点冗长,但我认为将视图模型与其他类型的常用模型进行比较以了解它们为什么不同以及为什么它们是必要的,这一点很重要。
总结并直接回答所问的问题:
一般来说,视图模型是一个包含呈现视图所需的所有属性和方法的对象。 视图模型属性通常与数据对象(如客户和订单)相关,此外,它们还包含与页面或应用程序本身相关的属性,例如用户名,应用程序名称等。视图模型提供了一个方便的对象,以传递给渲染引擎创建一个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; }
}
演示模型
演示模型是用于在屏幕或报告上呈现数据的实用程序类。 表示模型通常用于对来自多个DTO的数据构成的复杂数据结构进行建模。 表示模型通常代表数据的非规范化视图。
演示模型的特点:
•业务对象 - 它们的定义取决于应用程序数据。
•主要包含属性。 代码通常仅限于格式化数据或转换为DTO或从DTO转换。 演示模型不应包含业务逻辑。
•经常呈现数据的非规范化视图。 也就是说,他们经常结合多个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); } }
}
查看模型
视图模型与呈现模型相似,因为它是用于呈现视图的后备类。 然而,它与Presentation Model或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); } }
}
这种设计可能有效......但如果我们想创建一个页面来显示订单列表呢? 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; }
}
查看上面的两个类,我们可以看到,考虑视图模型的一种方式是它是一个包含另一个表示模型作为属性的表示模型。 顶层表示模型(即视图模型)包含与页面或应用程序相关的属性,而表示模型(属性)包含与应用程序数据相关的属性。
我们可以进一步推进我们的设计,创建一个基本视图模型类,它不仅可以用于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!
}
链接地址: http://www.djcxy.com/p/82107.html