ASP.NET MVC系列:Model 1. Model任务 2. 定义Model Metadata 3. 自定义Metadata验证属性 4. 模型绑定 5. ViewBag与ViewModel

  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)