打开MASA Blazor的正确姿势6:表单验证
对比Blazor内置的表单组件及其验证(EditForm),MASA Blaozr提供了功能更加丰富的表单组件及验证功能,即可以设置字段级验证,也可以设置表单级验证。而其中表单级验证,可以使用DataAnnotations和FluentValidation两种验证方式。
一、字段级验证
1、Rules属性
MASA Blazor所有输入型表单组件,如文本框、单选框、复选框、下拉框、开关、滑块等,都有一个Rules属性。Rules属性的值,是Func<string, StringBoolean>
类型的集合,即List<Func<string, StringBoolean>>
。
- Func委托的参数:类型为字符串,是当前表单组件的输入值。
- Func委托的返回值:类型为StringBoolean,可以返回true、false、或表示错误信息的字符串。返回true,表示验证通过;返回false,表示验证不通过;返回字符串,表示验证不通过,字符串为错误提示信息。
- 验证规则集合:List中每一个
Func<string, StringBoolean>
委托,表示一个验证规则,按顺序执行。需要注意的是,MASA Blazor的表单组件,默认每次只显示一条当时触发的错误提示信息,可以通过ErrorCount
设置。
2、字段级验证案例
<MTextField Label="标题" @bind-Value="title" Counter="20" Rules="titleRules"></MTextField>
<MTextField Label="正文" @bind-Value="content" Counter="50" Rules="contentRules"></MTextField>
@code{
private string title = "测试标题";
private string content = "测试内容";
//“标题”的验证规则只有一个
private List<Func<string, StringBoolean>> titleRules = new()
{
v => v.Length <= 20 ? true : "不得超过20个字符"
};
//“正文”的验证规则有两个,当输入值发生变化时,按顺序验证,视图层每次只能显示当前触发的错误
private List<Func<string,StringBoolean>> contentRules
{
get
{
var rules = new List<Func<string, StringBoolean>>();
//验证规则1和2
//返回true,表示验证通过;返回字符串,表示验证不通过,且返回错误信息
Func<string, StringBoolean> rule1 = (v) => v.Length <= 20 ? true : $"不得超过20个字符";
Func<string, StringBoolean> rule2 = (v) => v.Contains("hello") ? true : $"必须包含hello";
//将规则1和规则2加入到规则集合中
rules.Add(rule1);
rules.Add(rule2);
return rules;
}
}
}
注:案例中未使用MForm组件,但MForm和输入型表单组件的Rules可以一起使用,两者不冲突。
二、表单级验证
1、概述
虽然可以为每个表单组件定义单独的验证规则Rules,但使用还是比较繁琐。大多数情况下,我们主要使用表单级验证。通过MForm组件,以及DataAnnotations或者FluentValidation验证方式,实现更加高效的表单验证。
- MForm组件:MFrom组件相当于HTML原生标签form,或Blazor的内置组件EditForm,作为表单组件的父组件使用。MForm组件属性主要包括:Model-表单绑定及验证的对象,EnableValidation-开启表单验证,Value-是否通过表单验证,EnableI18n-支持检验信息的I18n多语言读取。
- DataAnnotations:AspNetCore内置的模型检验,通过特性方式,标注验证规则,前后端通用。直接引入:
@using System.ComponentModel.DataAnnotations
- FluentValidation:.NET中最常用的第三方验证库,前后端通用。安装nutget包后引用:
@using FluentValidation
2、DataAnnotations验证方式
<!--引用DataAnnotations验证-->
@using System.ComponentModel.DataAnnotations;
<!--
1、Model属性:为表单绑定一个数据对象。通过为数据对象设置验证规则,实现表单的验证功能
2、EnableValidation属性:布尔类型,开启表单验证功能
3、通过Type为submit的按钮,提交表单。如果通过验证,则执行HandleValidSubmit方法;否则执行HandleInvalidSubmit方法
-->
<MForm Model="stu" EnableValidation OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
<MTextField Label="姓名" @bind-Value="stu.Name" Counter="10"></MTextField>
<MTextField Label="年龄" @bind-Value="stu.Age" Type="number"></MTextField>
<MTextField Label="Email" @bind-Value="stu.Email" Type="email"></MTextField>
<MButton Type="submit">提交</MButton>
</MForm>
@code{
//表单一般绑定一个Model对象
private Student stu = new Student();
//使用特性标注,为表单绑定的对象设置验证规则
//Model一般使用独立的CS文件,此处仅仅是为了方便举例
class Student
{
[Required(ErrorMessage ="姓名必填!")]
[MaxLength(10,ErrorMessage ="不得超过10个字符!")]
public string? Name { get; set; }
[Range(0,100,ErrorMessage ="必须介于0至100!")]
public int Age { get; set; }
[EmailAddress(ErrorMessage ="必须输入Email")]
public string? Email { get; set; }
}
//提交表单,根据验证结果,执行相应逻辑
private void HandleValidSubmit()
{
Console.WriteLine("验证通过后的执行逻辑");
}
private void HandleInvalidSubmit()
{
Console.WriteLine("验证失败后的执行逻辑");
}
}
注:使用DataAnnotations验证方式,操作简便,但使Model与验证耦合。同时,DataAnnotations的验证规则也比较有限。
3、FluentValidation验证方式
<!--引用FluentValidation验证-->
@using FluentValidation;
<MForm Model="stu" EnableValidation OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
<!--...略,和DataAnnotations一样-->
</MForm>
@code{
private Student stu = new Student();
class Student
{
public string? Name { get; set; }
public int Age { get; set; }
public string? Email { get; set; }
}
//为Student类创建一个验证类
//验证类继承AbstractValidator<T>类,在构造函数中为Student类的属性设置验证规则
class StudentValidator : AbstractValidator<Student>
{
public StudentValidator()
{
RuleFor(s => s.Name).NotEmpty().WithMessage("姓名必填!").MaximumLength(10).WithMessage("不得超过10个字符");
RuleFor(s => s.Age).InclusiveBetween(0, 100).WithMessage("必须介于0至100!");
RuleFor(s => s.Email).EmailAddress().WithMessage("必须输入Email");
}
}
private void HandleValidSubmit()//...略,和DataAnnotations一样
}
注:使用FluentValidation验证方式,稍微繁琐些,但实现了Model类与验证的解耦,且提供了更加丰富的验证规则。
4、验证集合类型
1)DataAnnotations验证方式:
@using System.ComponentModel.DataAnnotations;
<MForm Model="stu" EnableValidation OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
......
<!--遍历初始化好的三个空的爱好项目-->
@foreach (var item in stu.Likes)
{
<MTextField Label="@($"爱好{item.Id}")" @bind-Value="item.LikeItem"></MTextField>
}
......
</MForm>
@code {
//初始化3个空的爱好项目
private Student stu = new Student()
{
Likes = new List<Like>
{
new Like{Id = 1},
new Like{Id = 2},
new Like{Id = 3}
}
};
class Student
{
[Required(ErrorMessage = "姓名必填!")]
[MaxLength(10, ErrorMessage = "不得超过10个字符!")]
public string? Name { get; set; }
[Range(0, 100, ErrorMessage = "必须介于0至100!")]
public int Age { get; set; }
[EmailAddress(ErrorMessage = "必须输入Email")]
public string? Email { get; set; }
//集合使用EnumerableValidation特性标注
[EnumerableValidation]
public List<Like> Likes { get; set; }
}
class Like
{
public int Id{ get; set; }
//集合的项目类型,一样使用特性方式标注验证规则
[Required(ErrorMessage = "爱好项目必填!")]
public string? LikeItem { get; set; }
}
......
}
2)FluentValidation验证方式:
@using FluentValidation;
<MForm Model="stu" EnableValidation OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
......
</MForm>
@code {
//初始化3个空的爱好项目
private Student stu = new Student()
{
Likes = new List<Like>
{
new Like{Id = 1},
new Like{Id = 2},
new Like{Id = 3}
}
};
//Model
class Student
{
public string? Name { get; set; }
public int Age { get; set; }
public string? Email { get; set; }
public List<Like>? Likes { get; set; }
}
class Like
{
public int Id{ get; set; }
public string? LikeItem { get; set; }
}
//验证规则
class StudentValidator : AbstractValidator<Student>
{
public StudentValidator()
{
RuleFor(s => s.Name).NotEmpty().WithMessage("姓名必填!").MaximumLength(10).WithMessage("不得超过10个字符");
RuleFor(s => s.Age).InclusiveBetween(0, 100).WithMessage("必须介于0至100!");
RuleFor(s => s.Email).EmailAddress().WithMessage("必须输入Email");
//为集合类型的属性设置验证规则
RuleForEach(s => s.Likes).SetValidator(new LikeValidator());
}
}
class LikeValidator : AbstractValidator<Like>
{
public LikeValidator()
{
RuleFor(l => l.LikeItem).NotEmpty().WithMessage("爱好项目必填!");
}
}
......
}
5、表单的验证方式
1)输入型表单组件的输入值变化时,会自动触发验证规则
2)使用Type类型为submit的按钮提交时,会触发表单验证(详见上面的所有案例,均使用这种方式)
3)通过@ref获取MForm对象,手动验证
@using FluentValidation;
<!--
1、Value属性为表单是否验证通过
2、通过@ref指令,获取MForm对象
-->
<MForm Model="stu" Value="isValidate" @ref="form" EnableValidation
OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
<MTextField Label="姓名" @bind-Value="stu.Name" Counter="10"></MTextField>
<MTextField Label="年龄" @bind-Value="stu.Age" Type="number"></MTextField>
<MTextField Label="Email" @bind-Value="stu.Email" Type="email"></MTextField>
<MButton OnClick="Validate">手动验证</MButton>
<MButton OnClick="ResetValidation">重置验证</MButton>
<MButton OnClick="Reset">重置表单</MButton>
</MForm>
@code {
private bool isValidate = true;
private MForm form;
......
//通过MForm对象的Validate()方法,手动触发验证
private void Validate()
{
form.Validate();
}
//通过MForm对象的ResetValidation()方法,手动重置验证
private void ResetValidation()
{
form.ResetValidation();
}
//通过MForm对象的Reset()方法,手动重置表单
private void Reset()
{
form.Reset();
}
......
}
6、使用I18n实现验证信息的多语言输出
1)安装I18n
- 在wwwroot目录下新建I18n文件夹
- 在I18n文件夹下分别创建zh-CN.json,en-US.json,supportedCultures.json文件
- 在入口文件Program.cs文件中,注册I18n服务
//zh-CN.json
{
"StudentValidateMessage": {
"NameRequired": "姓名必填!",
"NameLength": "姓名不得超过10个字符",
"AgeBetween": "年龄必须介于0至100",
"Email": "必须输入Email"
}
}
//en-US.json
{
"StudentValidateMessage": {
"NameRequired": "Name is Required",
"NameLength": "Name must smaller than 10",
"AgeBetween": "Age must between 0 and 100",
"Email": "Email must be EmailAddress"
}
}
//supportedCultures.json,排第一的项目为默认语言
[
"zh-CN",
"en-US"
]
//Program.cs
//1、Blazor Server模式,注册I18n服务
services.AddMasaBlazor().AddI18nForServer("wwwroot/I18n");
//2、Blazor WASM模式,注册I18n服务
await builder.Services.AddMasaBlazor().AddI18nForWasmAsync($"{builder.HostEnvironment.BaseAddress}/I18n");
2)通过依赖注入方式,使用I18n
@using FluentValidation;
@using BlazorComponent.I18n;
<MForm Model="stu" EnableValidation EnableI18n OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
......
</MForm>
@code {
private Student stu = new Student();
class Student
{
public string? Name { get; set; }
public int Age { get; set; }
}
class StudentValidator : AbstractValidator<Student>
{
//注入I18n
private readonly I18n i18n;
public StudentValidator(I18n i18n)
{
this.i18n = i18n;
//调用I18n的T()方法,读取多语言
RuleFor(s => s.Name).NotEmpty().WithMessage(i18n.T("StudentValidateMessage.NameRequired"));
......
}
}
......
}
7、灵活获取验证结果ValidationResults
无论是DataAnnotations,还是FluentValidation,都提供了获取ValidationResults的方法。但MASA Blaozr封装了创建验证对象的过程,所以ValidationResults还是不太好拿。不过,MForm通过插槽,将FormContext传递出来,我们可以通过FormContext获取验证结果的信息。
1)MForm提供的插槽如下所示:
2)通过FormContext获取验证结果信息
<MForm Model="stu" EnableValidation OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
<MTextField Label="姓名" @bind-Value="stu.Name" Counter="10"></MTextField>
<MTextField Label="年龄" @bind-Value="stu.Age" Type="number"></MTextField>
<MButton Type="submit">提交</MButton>
<!--
1、通过@context,获取插槽传递出来的FormContext
2、调用FormContext的EditContext属性,返回EditContext
3、调用EditContext的GetValidationMessages()方法,获取验证结果信息。是一个字符串集合,遍历获得所有验证信息。
-->
@foreach (var item in @context.EditContext.GetValidationMessages())
{
<h1>@item</h1>
}
</MForm>
三、输入型表单组件中与验证相关的属性
Rules
:设置验证规则HideDetails
:隐藏提示和验证错误等信息。true/false/auto,当设置为 auto 时,只有在有信息要显示时,才会显示信息。Error
:手动设置验证不通过ErrorCount
:一次性显示错误信息的条数,默认只显示一条ErrorMessages
:手动设置验证不通过的错误信息Success
:手动设置验证通过SuccessMessages
:手动设置验证通过的成功信息ValidateOnBlur
:?ValueExpression
:?