使用 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 生成数据库模型类。 图 1 显示了实体框架生成的 Movie 类。

Movie 实体

图 01:电影实体 (单击以查看全尺寸图像)

注意

若要了解有关使用实体框架生成数据库模型类的详细信息,请参阅名为使用实体框架创建模型类的教程。

控制器类

我们使用主控制器列出电影并创建新电影。 此类的代码包含在清单 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 () 操作。

请注意,第二个 Create () 操作包含以下代码行:

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

出现验证错误时,IsValid 属性返回 false。 在这种情况下,将重新显示包含用于创建电影的 HTML 窗体的“创建”视图。

创建分部类

Movie 类由实体框架生成。 如果在解决方案资源管理器窗口中展开 MoviesDBModel.edmx 文件并打开 MoviesDBModel.Designer,则可以看到 Movie 类的代码。代码编辑器中的 cs 文件 (请参阅图 2) 。

Movie 实体的代码

图 02:Movie 实体的代码 (单击以查看全尺寸图像)

Movie 类是分部类。 这意味着我们可以添加另一个同名的分部类来扩展 Movie 类的功能。 我们将验证逻辑添加到新的分部类。

将清单 2 中的 类添加到 Models 文件夹。

列表 2 - Models\Movie.cs

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

namespace MvcApplication1.Models
{

    public partial class Movie 
    {

    }
}

请注意,清单 2 中的 类包含 部分 修饰符。 添加到此类的任何方法或属性都将成为实体框架生成的 Movie 类的一部分。

添加 OnChanging 和 OnChanged 分部方法

当实体框架生成实体类时,实体框架会自动向该类添加分部方法。 实体框架生成与类的每个属性对应的 OnChanging 和 OnChanged 分部方法。

对于 Movie 类,实体框架将创建以下方法:

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

在更改相应的属性之前,将立即调用 OnChanging 方法。 更改属性后立即调用 OnChanged 方法。

可以利用这些分部方法将验证逻辑添加到 Movie 类。 清单 3 中的更新 Movie 类验证是否为 Title 和 Director 属性分配了非空值。

注意

分部方法是在类中定义的方法,不需要实现该方法。 如果不实现分部方法,编译器将删除方法签名和对方法的所有调用,因此不会产生与分部方法关联的运行时成本。 在Visual Studio Code编辑器中,可以通过键入关键字 (keyword) 分部后跟空格来添加分部方法,以查看要实现的分部列表。

列表 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 属性,则会向名为 _errors 的 Dictionary 分配一条错误消息。

此时,向 Title 属性分配空字符串并将错误添加到专用_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 框架使用模型联编程序 (DefaultModelBinder) 创建传递给 Create () 操作的 Movie 实例。 模型绑定器负责通过将 HTML 窗体字段绑定到 Movie 对象的实例来创建 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 中显示的页面演示了未为“标题”或“控制器”窗体字段输入任何值时会发生什么情况。

自动创建操作方法

图 03:缺少值的窗体 (单击以查看全尺寸图像)

请注意,DateReleased 值会自动验证。 由于 DateReleased 属性不接受 NULL 值,因此 DefaultModelBinder 会在此属性没有值时自动生成验证错误。 如果要修改 DateReleased 属性的错误消息,则需要创建自定义模型绑定器。

总结

本教程介绍了如何使用 IDataErrorInfo 接口生成验证错误消息。 首先,我们创建了一个分部 Movie 类,该类扩展了实体框架生成的部分 Movie 类的功能。 接下来,我们向 Movie 类 OnTitleChanging () 和 OnDirectorChanging () 分部方法添加了验证逻辑。 最后,我们实现了 IDataErrorInfo 接口,以便将这些验证消息公开给 ASP.NET MVC 框架。