ASP.NET MVC - 模型验证

时间:2023-02-02 17:42:59

ASP.NET MVC - 模型验证(Model verification)

模型验证原理浅析

模型验证用到了模型绑定器、模型验证器(System.Web.Mvc.DataAnnotationsModelValidator)、模型状态(System.Web.Mvc.ModelState)。为模型应用验证特性(一些特性类型)可以使模型具有同时在服务端和客户端验证数据合法性的功能。

当一个请求到达Action后,Action所接收的模型参数会被实例化。实例化时会调用模型验证器。模型验证器会验证提交的数据是否可以成功转化为Action方法所接收的参数类型,如果不能转化,则模型绑定会失败。如果提交的数据能够成功转化为Action所接收的参数类型,那么模型绑定器就会创建参数的实例,接着会进入应用在模型上的验证特性,由验证特性类对模型的数据进行进一步验证,如果验证特性类对数据的验证是失败的,那么错误信息会被自动写入到ModelState字典中。

现在假设用户发起一个请求,提交一个空的查询字符,而Action接收一个DateTime类型的参数,由于用户提交的是空值,而DateTime是不能为空的struct类型,所以模型验证器会验证失败,异常会抛出到客户端。如果这个数据是有效的,那么模型绑定器就会创建参数类型的实例,并将提交的数据绑定到实例上。

再假如用户提交一个表单,Action方法接收一个Order自定义类型,Order具有x和y属性。同样的,模型验证器会验证数据的有效性,如果验证通过,则由模型绑定器创建参数类型的实例,假如Order模型上应用了验证特性类,那么验证特性类就会对模型进行进一步验证,如果Order的x和y验证未通过,则会自动将x和y作为key,将错误信息作为value写入到ModelSate[x].Errors和ModelSate[y].Errors中,同时还会将错误的数据写入ModelSate[x]和ModelSate[y]中。如果视图页面使用了Html.ValidationMessage或Html.ValidationMessageFor,那这两个方法也会将它们的name参数当做key去ModelState中获取key所对应的错误信息,于是,错误信息显示在了视图上,如图:

ASP.NET MVC - 模型验证

客户端验证需要的文件

启用客户端验证(包括失焦时触发验证、配合Remote特性的Ajax验证)这需要引入:

<script src="/Scripts/jquery-1.10.2.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>

验证特性的本地化

只需要为每个验证特性的构造函数指定一个ErrorMessage的参数即可,如果需要多国语言的本地化验证提示信息,可参考:如何:为 ASP.NET 网页全球化设置区域性和 UI 区域性

各种验证特性


System.ComponentModel.DataAnnotations命名空间下的验证特性

[Required] 
//指出此属性是必填的;
[MinLength] 
//指出字符的最小长度;
[StringLength] 
//指出字符的最大长度,可在构造函数中指定最小长度;
[RegularExpression] 
//指出需要匹配的正则式;
[Range] 
//指出数字类型的属性的最小值和最大值;
[Compare] 
//指出此属性的值与参数指定的属性的值必须完全一样,比如用于验证两次输入的密码;
[HiddenInput] 
//指出这个属性是一个隐藏域;
[ScaffoldColumn] 
//指出客户端视图通过EditorForModel、DisplayForModel渲染出的Html的某些属性会被隐藏;
[ReadOnly] 
//指出服务端开始模型绑定时,不要把应用了此特性的属性更新为客户端提交的新值;
[UIHint] 
//指出一个模板名称,视图使用EditorFor、DispalyFor等模板辅助方法时,将应用此模板渲染Html,如果找不到指定的模板,ASP.NET MVC框架会自动使用默认模板进行替换,自定义模板参看:暂略;
[DisplayFormat] 
//指出格式化客户端输入框中的值;
[DataType] 
//指出输入框的输入模式;
//示例:
[DataType(DataType.Password)]
public string Password { get; set; }
DataType枚举可能的值如下
CreditCard
//表示信用卡卡号
Currency
//表示货币值
Custom
//表示自定义数据类型
Date
//表示的日期值
DateTime
//表示为日期和当天的时间
Duration
//表示存在的对象的持续时间
EmailAddress
//表示电子邮件地址
Html
//表示某一HTML文件
ImageUrl
//表示的图像的URL
MultilineText
//表示多行文本
Password
//表示密码值
PhoneNumber
//表示电话号码值
PostalCode
//表示邮政编码
Text
//表示显示文本
Time
//表示一个时间值
Upload
//表示文件上载的数据类型
Url
//表示一个URL值

DataType枚举可能的值如下


System.ComponentModel命名空间下的验证特性

[DisplayName] 
//指出此属性在视图的表单中显示的别名;
//示例:
[DisplayName("地址")]
public string Address { get; set; }

ASP.NET MVC - 模型验证 ASP.NET MVC - 模型验证


System.Web.Mvc命名空间下的验证特性

[Remote]
//指出客户端失焦时将路由到参数指定的控制器的Action中
//由Action做出处理,如果Action返回true则验证通过,否则验证失败
//如果失败,客户端会显示错误信息
//此特性专门用于客户端发起远程请求的Ajax验证
//客户端必须引入相应的Jquery文件
//注意,此特性只用于客户端Ajax的失焦验证,而并未执行正常流程的服务端验证
//所以如果需要服务端验证,则应在服务端应重写一个验证逻辑,因为假如js被禁用,
//此时如果点击提交,那么数据就不会经过Remoe指定的Action验证
//示例:
@{
        public class Order
    {
        public int OrderId { get; set; }
        [Remote("CheckUserName", "Account", ErrorMessage = "用户名已经存在")]
        public string UserName { get; set; }
    }     public class AccountController : Controller
    {
        public JsonResult CheckUserName(string userName)
        {
            List<string>
            userNameList = new List<string>
            {
                "sam","leo"
            };
            var result = !userNameList.Contains(userName);//数据库是否没有包含提交的用户名
            return Json(result, JsonRequestBehavior.AllowGet);
        }
    }
} <div class="form-group">
    @Html.LabelFor(model => model.UserName)
    @Html.EditorFor(model => model.UserName)
    @Html.ValidationMessageFor(model => model.UserName, "")
</div>

ASP.NET MVC - 模型验证

自定义验证

自定义验证特性 

推荐使用这种方式,因为可以结合客户端验证,而后面两种方式比较难用,权当了解。

using System.Web.Mvc;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations; namespace MusicStore.Models
{
    public class Order
    {
        public int OrderId { get; set; }
        public DateTime OrderDate { get; set; }
        [Remote("CheckUserName", "Account", ErrorMessage = "用户名已经存在")]
        public string UserName { get; set; }
        [Contains("中国", ErrorMessage = "字符中未包含中国二字")]
        [DisplayName("地址")]
        public string Address { get; set; }
    }     //验证提交的数据是否包含中国二字,从ValidationAttribute派生即可自定义验证特性,实现IClientValidatable即可注册客户端验证
    public class ContainsAttribute : ValidationAttribute, IClientValidatable
    {
        public string ContainsStr { get; set; }         public ContainsAttribute(string containsStr) : base($"字符中未包含{containsStr}")
        {
            ContainsStr = containsStr;
        }
        //为服务端验证编写逻辑
        protected override ValidationResult IsValid(object value, ValidationContext context)
        {
            return value == null ? ValidationResult.Success : value.ToString().Contains(ContainsStr) == false ? new ValidationResult($"字符中未包含{ContainsStr}") : ValidationResult.Success;
        }
        //为客户端验证进行注册
        //Contains特性验证可能应用在模型的多个属性或多个模型的属性上,所以此处以迭代器的方式返回多次调用
        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            //使用ModelClientValidationRule对象创建验证逻辑
            //ValidationType:指定客户端验证的函数的名称是什么,函数名称必须是小写
            //ErrorMessage:指定格式化的错误提示信息
            ModelClientValidationRule validationRule = new ModelClientValidationRule { ValidationType = "contains", ErrorMessage = FormatErrorMessage(metadata.DisplayName) };
            //客户端验证函数接收的参数,存储在params参数中,客户端通过params.containsvalue,参数名称必须是小写
            validationRule.ValidationParameters.Add("containsvalue", ContainsStr);
            yield return validationRule;
        }
    }
}
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript">
    $(document).ready(function () {         jQuery.validator.addMethod("contains",
            //测试输入的值是否包含服务端的特性验证指定的字符
            //inputValue:表单字段失焦后传递的输入的值
            //element:表单字段元素
            //params:通过服务端GetClientValidationRules方法生成的验证规则返回的验证特性参数的值
            function (inputValue, element, params) {
                var ContainsValue = params.containsvalue;               
                return inputValue.length ==  ? true : inputValue.indexOf(ContainsValue) == - ? false : true;
            }
        );         jQuery.validator.unobtrusive.adapters.add("contains", ["containsvalue"], function (options) {
            options.rules["contains"] = {
                containsvalue: options.params.containsvalue
            };            
            options.messages["contains"] = options.message;
        });     });   
</script>
<div class="form-group">
    @Html.LabelFor(model => model.Address)
    @Html.EditorFor(model => model.Address)
    @Html.ValidationMessageFor(model => model.Address, "")   
</div>

ASP.NET MVC - 模型验证

自定义自验证模型

自验证就是在模型中自定义一个验证逻辑,这比定义一个验证特性更简单,但在模型中定义的验证逻辑只适用于当前模型,不像验证特性可以应用在其它模型上,并且这种方式只能在服务端验证,无法实现客户端验证。

using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace MusicStore.Models
{
    public class Order:IValidatableObject
    {
        public int OrderId { get; set; }
        public string City { get; set; }
        public string Email { get; set; }

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            Order order = validationContext.ObjectInstance as Order;
            System.Text.RegularExpressions.Regex r = new System.Text.RegularExpressions.Regex("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@(\\w+\\.)+\\w{2,5})\\s*$");
            if (order == null)
            {
                yield break;
            }
            else if (!order.City.Contains("重庆"))
            {
                yield return new ValidationResult("城市中未包含重庆二字", new string[] { "City" });
            }
            else if (!r.IsMatch(order.Email))
            {
                yield return new ValidationResult("电邮格式错误", new string[] { "Email" });
            }
            //……
        }
    }
}

自定义参数验证

可以在控制器中编写一个验证方法,当Action接收到一个模型时,调用验证方法执行验证,如果验证失败则将错误信息写入ModelState中,此方法没有客户端验证。

[HttpPost]
public ActionResult Create(Order order)
{
    Validate(order);
    if (ModelState.IsValid)
    {
        //……
    }
    return View(order);
}
//自定义参数验证
private void Validate(Order order)
{
    System.Text.RegularExpressions.Regex r = new System.Text.RegularExpressions.Regex("^\\s*([A-Za-z0-9_-]+(\\.\\w+)*@(\\w+\\.)+\\w{2,5})\\s*$");
    if (!(order.City.Contains("重庆")))
    {
        ModelState.AddModelError("City", "城市中未包含重庆二字");
    }
    if (!(r.IsMatch(order.Email)))
    {
        ModelState.AddModelError("Email", "电邮格式错误");
    }
}

ASP.NET - 学习总目录