首页 文章

什么是MVC中的ViewModel?

提问于
浏览
373

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

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

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

11 回答

  • 536

    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 也可能在应用程序的存储过程或服务层中设置 . 因此视图模型中不需要 IdDateCreated . 当您查看员工的详细信息(已经被捕获的员工)作为静态文本时,您可能希望显示这两个属性 .

    加载视图/页面时,员工控制器中的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 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>
    

    因此,验证仅在 FirstNameLastName 上进行 . 使用Fluent 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");
         }
    }
    

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

    The key thing to remember is that the view model only represents the data that you want to use ,没别的 . 如果您拥有包含30个属性的域模型,并且只想更新单个值,则可以想象所有不必要的代码和验证 . 在这种情况下,您只能在视图模型中使用这一个值/属性,而不是域对象中的所有属性 .

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

    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
    

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

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

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

  • 68

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

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

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

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

    • 视图模型通过仅包含视图中表示的字段来记录视图 .

    • 视图模型可能包含使用数据注释或IDataErrorInfo的特定验证规则 .

    • 查看model定义了视图的外观(对于 LabelForEditorForDisplayFor 助手) .

    • 视图模型可以组合来自不同数据库实体的值 .

    • 您可以为视图模型轻松指定显示模板,并使用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; }
    }
    

    恢复:

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

    Edit: I updated this answer on my Blog:

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

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

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

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

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

    Data Transfer Objects a.k.a “Model”

    数据传输对象(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; }
    }
    

    Presentation Models

    表示模型是一个实用程序类,用于在屏幕或报表上呈现数据 . 表示模型通常用于对来自多个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); } }
    }
    

    View Models

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

    视图模型的特征:

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

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

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

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

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

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

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

    查看模型组成

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

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

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

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

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

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

    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!
    }
    
  • 14

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

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

  • 10

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

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

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

  • 10

    MVC没有viewmodel:它有一个模型,视图和控制器 . viewmodel是MVVM(Model-View-Viewmodel)的一部分 . MVVM源自Presentation Model,并在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; }
    }
    

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

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

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

  • 22

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

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

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

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

    以下几个View Models的例子

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

    • 视图模型可以仅定义视图所需的单个实体的特定字段 .

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

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

    这是List data using ViewModel的一个小例子 .

    希望这会有用 .

  • 1

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

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

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

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

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

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

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

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

    @model ProjectName.Model.StudentViewModel

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

  • 10

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

  • 4

    视图模型是数据的概念模型 . 它的用途是例如获取子集或组合来自不同表的数据 .

    您可能只需要特定属性,因此这允许您仅加载那些而不是其他不必要的属性

  • 1
    • ViewModel包含在视图中表示的字段(对于LabelFor,EditorFor,DisplayFor帮助程序)

    • ViewModel可以使用数据注释或IDataErrorInfo具有特定的验证规则 .

    • ViewModel可以有来自不同数据模型或数据源的多个实体或对象 .

    Designing ViewModel

    public class UserLoginViewModel 
    { 
    [Required(ErrorMessage = "Please enter your username")] 
    [Display(Name = "User Name")]
    [MaxLength(50)]
    public string UserName { get; set; }
     [Required(ErrorMessage = "Please enter your password")]
     [Display(Name = "Password")]
     [MaxLength(50)]
     public string Password { get; set; } 
    }
    

    Presenting the viewmodel in the view

    @model MyModels.UserLoginViewModel 
    @{
     ViewBag.Title = "User Login";
     Layout = "~/Views/Shared/_Layout.cshtml";
    }
    @using (Html.BeginForm())
    {
    <div class="editor-label">
     @Html.LabelFor(m => m.UserName)
    </div>
    <div class="editor-field">
     @Html.TextBoxFor(m => m.UserName)
     @Html.ValidationMessageFor(m => m.UserName)
    </div>
    <div class="editor-label">
     @Html.LabelFor(m => m.Password)
    </div>
    <div class="editor-field">
     @Html.PasswordFor(m => m.Password)
     @Html.ValidationMessageFor(m => m.Password)
    </div>
    <p>
     <input type="submit" value="Log In" />
    </p>
    </div>
    }
    

    Working with Action

    public ActionResult Login()
    { 
    return View();
    }
    [HttpPost]
    public ActionResult Login(UserLoginViewModel user)
    {
    // To acces data using LINQ
    DataClassesDataContext mobjentity = new DataClassesDataContext();
     if (ModelState.IsValid) 
    { 
    try
     {
     var q = mobjentity.tblUsers.Where(m => m.UserName == user.UserName && m.Password == user.Password).ToList(); 
     if (q.Count > 0) 
     { 
     return RedirectToAction("MyAccount");
     }
     else
     {
     ModelState.AddModelError("", "The user name or password provided is incorrect.");
     }
     }
     catch (Exception ex)
     {
     } 
     } 
     return View(user);
    }
    
    • 在ViewModel中,仅放置要在视图/页面上显示的字段/数据 .

    • 由于视图重复了ViewModel的属性,因此很容易进行渲染和维护 .

    • 当ViewModel变得更复杂时使用映射器 .

相关问题