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所对应的错误信息,于是,错误信息显示在了视图上,如图:
客户端验证需要的文件
启用客户端验证(包括失焦时触发验证、配合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; }
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; }
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>
自定义验证
自定义验证特性
推荐使用这种方式,因为可以结合客户端验证,而后面两种方式比较难用,权当了解。
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>
自定义自验证模型
自验证就是在模型中自定义一个验证逻辑,这比定义一个验证特性更简单,但在模型中定义的验证逻辑只适用于当前模型,不像验证特性可以应用在其它模型上,并且这种方式只能在服务端验证,无法实现客户端验证。
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中,此方法没有客户端验证。
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", "电邮格式错误");
}
}