ASP.NET MVC系列:Model
1. Model任务
Model负责通过数据库、AD(Active Directory)、Web Service及其他方式获取数据,以及将用户输入的数据保存到数据库、AD、Web Service等中。
Model只专注于有效地提供数据访问机制、数据格式验证、业务逻辑验证等。
2. 定义Model Metadata
Metadata用于定义数据模型的相关属性,如:显示名称、数据长度及数据格式验证等。利用System.ComponentModel.DataAnnotations中的DataAnnotations机制对ASP.NET MVC数据模型进行辅助定义。
System.ComponentModel.DataAnnotations命名空间的验证属性包括:StringLength、Required、RegularExpression及Range等。
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Libing.Portal.Web.Models { public class Product { public int ProductID { get; set; } [DisplayName("产品名称")] [Required(ErrorMessage = "产品名称不能为空")] [StringLength(100, ErrorMessage = "产品名称最大长度100个字符")] public string ProductName { get; set; } [Required] [RegularExpression(@"^\d+$", ErrorMessage = "库存数量只能为数字")] [Range(0, 100, ErrorMessage = "库存数量0至100之间")] public int UnitsInStock { get; set; } } }
3. 自定义Metadata验证属性
定义Metadata验证属性:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel.DataAnnotations; namespace Libing.Portal.Web.Models.Attributes { /// <summary> /// 验证正整数 /// </summary> public class PositiveIntegerAttribute : RegularExpressionAttribute { public PositiveIntegerAttribute() : base(@"^\d+$") { } } }
使用自定义Metadata验证属性:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using Libing.Portal.Web.Models.Attributes; namespace Libing.Portal.Web.Models { public class Product { [Required] [PositiveInteger(ErrorMessage = "库存数量只能为正整数")] [Range(0, 100, ErrorMessage = "库存数量0至100之间")] public int UnitsInStock { get; set; } } }
4. 模型绑定
ASP.NET MVC通过模型绑定(Model Binding)机制来解析客户端传送过来的数据,解析的工作由DefaultModelBinder类进行处理。若要自定义ModelBinder类行为,需实现IModelBinder接口。
4.1 简单模型绑定
Action的参数在Action被执行时会通过DefaultModelBinder从form或QueryString传送过来的数据进行处理,即将传送过来的字符串型的数据转换成对应的.Net类,并将其输入Action。
public ActionResult Index(string UserName) { ViewBag.UserName = UserName; return View(); }
4.2 复杂模型绑定
在ASP.NET MVC中,可以通过DefaultModelBinder类将form数据对应到复杂的.NET类,即模型。该模型可能是一个List<T>类或一个含有多个属性的自定义类。
public ActionResult Index(Product product) { ViewBag.ProductName = product.ProductName; return View(); }
从客户端传送过来的form数据会通过DefaultModelBinder类自动创建Product类对象,将form字段通过.NET的Reflection机制一一对应到对象的同名属性中。
4.3 模型绑定数据验证
ASP.NET MVC在处理模型绑定时,会处理Model的数据验证。模型绑定的数据验证失败,则Controller的ModelState.IsValid验证值为false。
[HttpPost] public ActionResult Create(Product product) { if (ModelState.IsValid) { return RedirectToAction("Details", new { id = product.ProductID }); } return View(); }
可以使用ModelState.AddModelError()方法在Controller中判断更加复杂的业务逻辑,并自定义错误信息至ModelState。
[HttpPost] public ActionResult Create(Product product) { if (ModelState.IsValid) { if (product.UnitsInStock <=10) { ModelState.AddModelError("UnitsInStock", "UnitsInStock必须大于10"); return View(); } return RedirectToAction("Details", new { id = product.ProductID }); } return View(); }
4.4 使用Bind属性限制可被更新的Model属性
复杂模型绑定的验证,在默认情况下,不管Model中有多少字段,只要客户端form有数据传送过来就会自动进行绑定。在ASP.NET MVC中可以通过使用Bind属性限制可被更新的Model属性。
4.4.1 绑定多个字段中的部分字段
通过Bind属性来定义Model中需要绑定哪些字段。
示例:不包括的自动绑定的属性
[HttpPost] public ActionResult Create([Bind(Exclude = "ProductID")] Product product) { if (ModelState.IsValid) { if (product.UnitsInStock <= 10) { ModelState.AddModelError("UnitsInStock", "UnitsInStock必须大于10"); return View(); } return RedirectToAction("Details", new { id = product.ProductID }); } return View(); }
示例:多个不包括的自动绑定的属性
多个字段需要排除,使用逗号(,)分隔。
[HttpPost] public ActionResult Create([Bind(Exclude = "ProductID,CreateDate")] Product product) { if (ModelState.IsValid) { if (product.UnitsInStock <= 10) { ModelState.AddModelError("UnitsInStock", "UnitsInStock必须大于10"); return View(); } return RedirectToAction("Details", new { id = product.ProductID }); } return View(); }
示例:使用Include指定需要绑定的字段
[HttpPost] public ActionResult Create([Bind(Include = "ProductName,UnitsInStock")] Product product) { if (ModelState.IsValid) { if (product.UnitsInStock <= 10) { ModelState.AddModelError("UnitsInStock", "UnitsInStock必须大于10"); return View(); } return RedirectToAction("Details", new { id = product.ProductID }); } return View(); }
如果不希望在每个Action的参数中都应用Bind属性,可以在Model定义中指定。
示例:在Model中设置Bind属性
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace Libing.Portal.Web.Models { [Bind(Include = "ProductName,UnitsInStock")] public class Product { public int ProductID { get; set; } public string ProductName { get; set; } public int UnitsInStock { get; set; } public DateTime CreateDate { get; set; } } }
4.4.2 UpdateModel()方法与TryUpdateModel()方法
当绑定引发异常时,使用UpdateModel()方法会直接抛出异常。使用TryUpdateModel()方法,则会在验证成功时返回true,失败或发生异常时返回false。
示例:UpdateModel()
[HttpPost] public ActionResult Create(Product product) { UpdateModel(product); return RedirectToAction("Details", new { id = product.ProductID }); }
示例:TryUpdateModel()
[HttpPost] public ActionResult Create(Product product) { if (TryUpdateModel(product)) { return RedirectToAction("Details", new { id = product.ProductID }); } return View(); }
5. ViewBag与ViewModel
5.1 使用ViewBag
Controller基类提供了ViewBag,可用于将数据项从Controller传递到View中。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Libing.Portal.Web.Models; namespace Libing.Portal.Web.Controllers { public class ProductController : Controller { private PortalContext context = new PortalContext(); public ActionResult Edit(int id) { Product product = context.Products.Find(id); ViewBag.Categories = new SelectList(context.Categories, "CategoryID", "CategoryName", product.CategoryID); return View(product); } protected override void Dispose(bool disposing) { context.Dispose(); base.Dispose(disposing); } } }
@Html.DropDownList("CategoryID", ViewBag.Categories as SelectList)
5.2 使用ViewModel模式
ViewModel模式创建强类型的类,对特定View情形优化,并向View模板提供所需要的动态值或内容。Controller将这些ViewModel传递到View模板中显示。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace Libing.Portal.Web.Models.ViewModels { public class ProductViewModel { public Product Product { get; set; } public SelectList Categories { get; set; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Libing.Portal.Web.Models; using Libing.Portal.Web.Models.ViewModels; namespace Libing.Portal.Web.Controllers { public class ProductController : Controller { private PortalContext context = new PortalContext(); public ActionResult Edit(int id) { Product product = context.Products.Find(id); ProductViewModel productViewModel = new ProductViewModel(); productViewModel.Product = product; productViewModel.Categories = new SelectList(context.Categories, "CategoryID", "CategoryName", product.CategoryID); return View(productViewModel); } protected override void Dispose(bool disposing) { context.Dispose(); base.Dispose(disposing); } } }
@model Libing.Portal.Web.Models.ViewModels.ProductViewModel
@Html.DropDownList("CategoryID", Model.Categories)