后端永远不要相信前端。
所以ASP.NET内置了Model验证(Validation)模块,验证用户的输入是否符合要求。
以“必填(Required)”为例,演示:Name不能为空
首先在属性上添加ASP.NET内置的特性(Annotation)
[Required(ErrorMessage = "* 必须填写")] public string UserName { get; set; }
RequiredAttribute和我们之前使用的DataType、Display等属于同一个Assembly:System.ComponentModel.Annotations.dll
[Display(Name = "自我介绍")] //为label填充文本 public string SelfDescription { get; set; }
注意:ErrorMessage是错误提示信息,不填写的话用默认的英语提示。
if (!ModelState.IsValid) { return View(student); }
ModelState中包含两种错误信息:
razor view上添加错误提示的HtmlHelper
@Html.ValidationMessageFor(m => m.Name)
不要忘了这个ValidationMessageFor标签,否则错误信息(ErrorMessage)不会呈现。
[MaxLength(8, ErrorMessage = "* 最大长度不能超过8")] [MinLength(4, ErrorMessage = "* 最小长度不能少于4")]有时候我们需要同时限制最大最小,可以使用:
[StringLength(8, MinimumLength = 4)]
或者限定为一个固定长度:
[StringLength(4, MinimumLength = 4)]
[Range(1950, 2020)]同时限制了非数字的输入,即所有非数字输入都会因此而报错。
[Url] [EmailAddress] [RegularExpression("[0-9]*")]
不同验证特性可以累加,但只要一个没通过,后面的就不再进一步验证。
使用DataAnnotations(而不是System.Web.Mvc)中的CompareAttribute:
using CompareAttribute = System.ComponentModel.DataAnnotations.CompareAttribute;必须指定要和哪一个属性进行比较:
//[Compare("Name")] [Compare(nameof(Name))] //更优雅的写法 public string ConfirmName { get; set; }
在Required中有一个属性:AllowEmptyStrings,默认为false,即空格输入也被视为没有输入。
可以将其改为true,这样用户的纯空格输入就会视为有输入内容
[Required(ErrorMessage = "* 姓名必填", AllowEmptyStrings = true)]
PS:早期的一些ASP.NET版本,当AllowEmptyStrings = true 时还需要配合
[DisplayFormat(ConvertEmptyStringToNull = false)]
除了可以按属性一条一条的显示错误消息,还可以利用
把所有的ErrorMessages集中显示。
演示:要求radio也被选中
[Required(ErrorMessage = "* 至少选一个")] public bool? IsMale { get; set; } //注意这个“可空”
<label> @Html.RadioButtonFor(m => m.IsMale, true) 男 </label> <label> @Html.RadioButtonFor(m => m.IsMale, false) 女 </label>
再引入:
@Html.ValidationSummary()
<span class="field-validation-error" data-valmsg-for="NewUser.Name" data-valmsg-replace="true">* 用户名不能为空</span>它带有一个field-validation-error,可以通过定义其CSS:
.field-validation-error { color: red; font-size: 12px; }
有时候我们还有更多的想法,比如加一个图标。
注意不能在ErrorMessage中直接添加图标内容:
[Required(ErrorMessage = "<span class='fa fa-icon'></span> 姓名必填")]
ASP.NET会将HTML内容“编码”后输出,使其不会被按HTML解析:(复习HTML转义字符)
<span class='fa fa-icon'></span>而是应该按照图标生成的CSS原理(字体和伪类,复习)添加样式:
.field-validation-error { font-family: "Glyphicons Halflings"; } .field-validation-error::before { content: "\e107"; }演示:先生成一个图标,再复制其CSS样式
有时候我们的验证规则不能由内置的Attribute完成。比如:用户名是否重复,需要查询数据库之后才能确认。
这时候就需要使用ModelState.AddModelError()方法:
if (true)//通过数据库的查询,发现student.Name重复了 { //ModelState.AddModelError("Name", "* 用户名重复"); //ModelState.AddModelError(nameof(student.Name), "* 用户名重复"); return View(student); }
注意AddModelError()的第二个参数为ErrorMessage,
第一个参数可以是:
ModelState.AddModelError("", "* 用户名重复");只有在Summary中显示。ValidationSummary还可以排除掉属性级错误(excludePropertyErrors):
@Html.ValidationSummary(true)
另外,如果Model中使用了“嵌套”属性(比如:InvitedBy.Username),AddModelError()的第一个参数也应该是相应的由小数点分割的:InvitedBy.Username。
因为这样才能对应上最后呈现的原生HTML页面上Form表单控件的name属性:
<input type="text" data-val="true" data-val-required="* 邀请人必须填写" id="InvitedBy_Username" name="InvitedBy.Username" value="" />
观察由ASP.NET HtmlHelper生成的表单元素:
<input type="text" data-val="true" data-val-required="* 必须填写" id="Register_UserName" name="Register.UserName" value="">
以及他们相应的错误消息元素:
<span class="field-validation-valid" data-valmsg-for="Register.UserName" data-valmsg-replace="true"></span>
为什么会有这些以 data-val 开头的属性呢?
为了集成服务器/客户端验证,微软基于jquery validation(复习)开发出jquery.validate.unobtrusive.js插件(以下简称unobtrusive)。
生成的form表单元素中data-开头的属性,都是为unobtrusive准备的。unobtrusive利用:
还可以有验证参数,比如使用[MinLength(4)]之后,会有属性:
data-valmsg-for="inputname"等也是类似的作用。
然后,unobtrusive根据以上属性自动设定rule并进行验证。
演示:在HTML页面再引入unobtrusive(一样要注意引入顺序,必须在validate之后),不需要任何额外的代码,就能实现验证效果。
因为unobtrusive.js是基于jquery.validate.js的,所以validate上的那些操作(比如:rules()/addMethod()……仍然有效)
但是,unobtrusive在页面加载时就已经进行了一次激活($('form').validate()),解析所有form表单元素,并生成rules。所以
可以直接使用rules(),比如:
$(document).ready(function () { $('#UserName').rules('add', { lessThan: '#threshold' //lessThan是自定义method
而且,MVC生成的data-val*属性不同于validate.js中的属性标记,他们都已经被解析(parse),所以可以直接remove:
$('#UserName').rules('remove')
但是如果页面是Ajax加载的,就不会有这么一个parse的过程。
演示:
$("#ajaxContainer").load("/Register/_UserName");
$.validator.unobtrusive.parse($('form'));
@想一想@:为什么?该怎么办呢?
通过
已有的验证Attribute实现(简单改动),性价比高!
最常利用的就是RegularExpressionAttribute,比如:自定义的整数验证(正负皆可)
public class IntegerAttribute : RegularExpressionAttribute
然后,构造函数中传入自定义的正则表达式:
public IntegerAttribute() : base(@"^-?\d+$")
还可以自定义其error message:
public override string FormatErrorMessage(string name) { //name为[Display(Name)](如果声明的话),否则为属性名 return $"{name}不是整数";
演示:
[Integer] public string Name { get; set; }
注意:观察生成的HTML标签,此时还没有客户端的JavaScript实现。
还需要最后一步,在Global.asax的Application_Start()中:
DataAnnotationsModelValidatorProvider.RegisterAdapter( typeof(IntegerAttribute), typeof(RegularExpressionAttributeAdapter));另外一种方式是
的,通过继承ValidationAttribute和实现IClientValidatable
public class AtLeastOneRequired : ValidationAttribute, IClientValidatable
让生成的input标签中添加validation相关的属性(略)……
protected override ValidationResult IsValid( object value, ValidationContext validationContext) { bool atLeastOneNotNull = //all properties validationContext.ObjectType.GetProperties() //with Group attributes .Where(p => p.IsDefined(typeof(AtLeastOneRequired), true)) //only in current group .Where(p => p.GetCustomAttributesData().Any( a => a.ConstructorArguments.Count > 0 && a.ConstructorArguments.Any(aca => aca.Value.ToString() == _group)) ) //at least one is not null .Any(p => p.GetValue(validationContext.ObjectInstance, null) != null) ; return atLeastOneNotNull ? null : new ValidationResult(base.ErrorMessage); }
public IEnumerable<ModelClientValidationRule> GetClientValidationRules( ModelMetadata metadata, ControllerContext context) { ModelClientValidationRule rules = new ModelClientValidationRule { ValidationType = "atleastonerequire", ErrorMessage = FormatErrorMessage(metadata.DisplayName) }; rules.ValidationParameters["group"] = _group; yield return rules; }
然后,在前台还要将自定义的method和rule“适配”(adapter)起来(略)……
$.validator.unobtrusive.adapters.add("atleastonerequire", ["group"], function (options) { options.rules["atleastonerequire"] = options.params.group; options.messages["atleastonerequire"] = options.message; });
因为缺乏对unobtrusive的全面认识,从逻辑上看有些难以理解,
演示:一个带有DropdownList的页面,options由GET Action填充,Post之后抛出NullReference异常
@想一想@:为什么呢?因为:
DropdownList的生成需要数据(Options集合),这个数据是在GET Action里被赋值的。但它不会自动被“自动记录”,POST之后页面需要重新生成,这时候POST Action中没有为Options赋值的过程,自然就报错了。
如何解决这个问题呢?当然可以在POST的Action里再一次给Options赋值,但是更常见的办法是重定向(Redirect)一次。
这种对所有的Post请求(即使是回到当前页面,也要使用Redirect),都按照Post-Redirect-Get进行处理的模式被称之为PRG,目前被广泛使用。它除了能解决上述问题,还可以:
可以使用以下
相关方法:
return RedirectToAction(nameof(Index));还可以有Controller名、routeData等:
//HomeController的Index() Action,还附带一个route data: id=7 return RedirectToAction("Index", "Home", new { id = 7 });
return Redirect("http://163.com/");
return RedirectToRoute("");
return RedirectToActionPermanent("Index","Home", new { id = 10, name= "fg"});
但是,这样做似乎仍然有问题:验证错误提示呢?
然而,当Model验证失败时,重定向会丢失ModelState的Error信息,导致验证错误信息无法显示。(演示)
怎么办?我们可以:
TempData[KEY_OF_ERROR] = ModelState;
ModelState.Merge(TempData[KEY_OF_ERROR] as ModelStateDictionary);
注意:
TempData几乎是为PRG量身定做的:
多快好省!前端后端,线上线下,名师精讲
更多了解 加: