ASP.NET MVC系列:Model

libingql發表於2015-07-11

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)

相關文章