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)