共用方式為


驗證與 IDataErrorInfo 介面 (C#)

作者:Stephen Walther

Stephen Walther 示範如何在模型類別中實作 IDataErrorInfo 介面來顯示自訂驗證錯誤訊息。

此教學課程的目標是說明一種在 ASP.NET MVC 應用程式中執行驗證的方法。 您會了解如何防止某人提交 HTML 表單,而不需要提供必要表單欄位的值。 在此教學課程中,您會了解如何使用 IErrorDataInfo 介面來執行驗證。

假設

在此教學課程中,我會使用 MoviesDB 資料庫和 Movies 資料庫資料表。 此資料表具有下列資料行:

資料行名稱 資料類型 允許 Null
Id int False
標題 Nvarchar(100) False
主管 Nvarchar(100) False
DateReleased Datetime False

在此教學課程中,我使用 Microsoft Entity Framework 產生資料庫模型類別。 Entity Framework 所產生的 Movie 類別會顯示在圖 1 中。

Movie 實體

圖 01:電影實體 (按一下以檢視全尺寸影像)

注意

若要深入了解如何使用 Entity Framework 產生資料庫模型類別,請參閱我的教學課程,標題為「使用 Entity Framework 建立模型類別」。

控制器類別

我們使用 Home 控制器來列出電影,並建立新的電影。 此類別的程式碼包含在清單 1 中。

清單 1 – Controllers\HomeController.cs

using System.Linq;
using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{

    public class HomeController : Controller
    {
        private MoviesDBEntities _db = new MoviesDBEntities();

        public ActionResult Index()
        {
            return View(_db.MovieSet.ToList());
        }

        public ActionResult Create()
        {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Movie movieToCreate)
        {
            // Validate
            if (!ModelState.IsValid)
                return View();

            // Add to database
            try
            {
                _db.AddToMovieSet(movieToCreate);
                _db.SaveChanges();

                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

    }
}

清單 1 中的 Home 控制器類別包含兩個 Create() 動作。 第一個動作會顯示用於建立新電影的 HTML 表單。 第二個 Create() 動作會執行將新電影實際插入資料庫中的操作。 第二個 Create() 動作會在第一個 Create() 動作所顯示的表單提交至伺服器時被叫用。

請注意,第二個 reate() 動作包含下列幾行程式碼:

// Validate
if (!ModelState.IsValid)
    return View();

當發生驗證錯誤時,IsValid 屬性會傳回 false。 在此情況下,會重新顯示包含建立電影之 HTML 表單的 [建立] 檢視表。

建立局部類別

Movie 類別是由 Entity Framework 產生。 如果您在方案總管視窗中展開 MoviesDBModel.edmx 檔案,並在程式碼編輯器中開啟 MoviesDBModel.Designer.cs 檔案,則可以查看 Movie 類別的程式碼 (參見圖 2)。

Movie 實體的程式1碼

圖 02:電影實體的程式碼 (按一下以檢視全尺寸影像)

Movie 類別是局部類別。 這表示我們可以新增另一個具有相同名稱的局部類別,以擴充 Movie 類別的功能。 我們會將驗證邏輯新增至新的局部類別。

將清單 2 中的類別新增至 [模型] 資料夾。

清單 2 - Models\Movie.cs

using System.Collections.Generic;
using System.ComponentModel;

namespace MvcApplication1.Models
{

    public partial class Movie 
    {

    }
}

請注意,清單 2 中的類別包含局部修飾詞。 您新增至此類別的任何方法或屬性,都會成為 Entity Framework 所產生的 Movie 類別的一部分。

新增 OnChanging 和 OnChanged 局部方法

當 Entity Framework 產生實體類別時,Entity Framework 會自動將局部方法新增至類別。 Entity Framework 會產生 OnChanging 和 OnChanged 局部方法,這些方法會對應至類別的每個屬性。

在 Movie 類別的情況下,Entity Framework 會建立下列方法:

  • OnIdChanging
  • OnIdChanged
  • OnTitleChanging
  • OnTitleChanged
  • OnDirectorChanging
  • OnDirectorChanged
  • OnDateReleasedChanging
  • OnDateReleasedChanged

OnChanging 方法會在對應的屬性變更之前被呼叫。 在屬性變更後,會立即呼叫 OnChanged 方法。

您可以利用這些局部方法,將驗證邏輯新增至 Movie 類別。 清單 3 中的更新 Movie 類別,會驗證 Title 和 Director 屬性是否已獲指派無空值。

注意

局部方法是在您不需要實作的類別中定義的方法。 如果您未實作局部方法,則編譯程式會移除方法簽章和方法的所有呼叫,因此不會有與局部方法關聯的執行階段成本。 在 Visual Studio Code 編輯器中,您可以輸入關鍵字 partial,後面接著空格來檢視要實作的局部清單,藉以新增局部方法。

清單 3 - Models\Movie.cs

using System.Collections.Generic;
using System.ComponentModel;

namespace MvcApplication1.Models
{

    public partial class Movie : IDataErrorInfo
    {
        private Dictionary<string, string> _errors = new Dictionary<string, string>();

        partial void OnTitleChanging(string value)
        {
            if (value.Trim().Length == 0)
                _errors.Add("Title", "Title is required.");
        }

        partial void OnDirectorChanging(string value)
        {
            if (value.Trim().Length == 0)
                _errors.Add("Director", "Director is required.");
        }

    }
}

例如,您若嘗試將空字串指派給 Title 屬性,則會將錯誤訊息指派給 named _errors 字典。

此時,當您將空字串指派給 Title 屬性,並將錯誤新增至 private _errors 欄位時,實際上什麼事都不會發生。 我們需要實作 IDataErrorInfo 介面,將這些驗證錯誤公開至 ASP.NET MVC 架構。

實作 IDataErrorInfo 介面

自第一個版本以來,IDataErrorInfo 介面一直是 .NET Framework 的一部分。 此介面是非常簡單的介面:

public interface IDataErrorInfo
{
    string this[string columnName] { get; }

    string Error { get; }
}

如果類別實作 IDataErrorInfo 介面,ASP.NET MVC 架構會在建立類別的執行個體時使用此介面。 例如,Home 控制器 Create() 動作會接受 Movie 類別的執行個體:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Movie movieToCreate)
{
    // Validate
    if (!ModelState.IsValid)
        return View();

    // Add to database
    try
    {
        _db.AddToMovieSet(movieToCreate);
        _db.SaveChanges();

        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

ASP.NET MVC 架構會使用模型繫結器 (the DefaultModelBinder) 建立傳遞至 Create() 動作的 Movie 執行個體。 模型繫結器負責建立 Movie 物件的執行個體,方法是將 HTML 表單欄位繫結至 Movie 物件的執行個體。

DefaultModelBinder 會偵測類別是否實作 IDataErrorInfo 介面。 如果類別實作這個介面,則模型繫結器會針對類別的每個屬性叫用 IDataErrorInfo.this 索引器。 如果索引器傳回錯誤訊息,則模型繫結器會自動將此錯誤訊息新增至模型狀態。

DefaultModelBinder 也會檢查 IDataErrorInfo.Error 屬性。 這個屬性的目的是代表與類別相關聯的非屬性特定驗證錯誤。 例如,您可能想要強制執行驗證規則,該規則取決於 Movie 類別的多個屬性值。 在此情況下,您會從 Error 屬性傳回驗證錯誤。

清單 4 中更新的 Movie 類別會實作 IDataErrorInfo 介面。

清單 4 - Models\Movie.cs (實作 IDataErrorInfo)

using System.Collections.Generic;
using System.ComponentModel;

namespace MvcApplication1.Models
{

    public partial class Movie : IDataErrorInfo
    {
        private Dictionary<string, string> _errors = new Dictionary<string, string>();

        partial void OnTitleChanging(string value)
        {
            if (value.Trim().Length == 0)
                _errors.Add("Title", "Title is required.");
        }

        partial void OnDirectorChanging(string value)
        {
            if (value.Trim().Length == 0)
                _errors.Add("Director", "Director is required.");
        }

        #region IDataErrorInfo Members

        public string Error
        {
            get
            {
                return string.Empty;
            }
        }

        public string this[string columnName]
        {
            get
            {
                if (_errors.ContainsKey(columnName))
                    return _errors[columnName];
                return string.Empty;
            }
        }

        #endregion
    }
}

在清單 4 中,索引器屬性會檢查 _errors 集合,以查看它是否包含對應至索引器之屬性名稱的索引鍵。 如果沒有與屬性相關聯的驗證錯誤,則會傳回空字串。

您不需要以任何方式修改 Home 控制器,即可使用修改過的 Movie 類別。 圖 3 中顯示的頁面說明了當 Title 或 Director 表單欄位未輸入任何值時,會發生什麼事。

自動建立動作方法

圖 03:含有遺漏值的表單 (按一下以檢視全尺寸影像)

請注意,會自動驗證 DateReleased 值。 因為 DateReleased 屬性不接受 NULL 值,所以當此屬性沒有值時,DefaultModelBinder 會自動產生此屬性的驗證錯誤。 如果您想要修改 DateReleased 屬性的錯誤訊息,則需要建立自訂模型繫結器。

摘要

在此教學課程中,您已了解如何使用 IDataErrorInfo 介面來產生驗證錯誤訊息。 首先,我們建立的局部 Movie 類別,會延伸 Entity Framework 所產生的局部 Movie 類別的功能性。 接著,我們已將驗證邏輯新增至 Movie 類別 OnTitleChanging() 和 OnDirectorChanging() 局部方法。 最後,我們實作了 IDataErrorInfo 介面,以便將這些驗證訊息公開至 ASP.NET MVC 架構。