打开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>


三、输入型表单组件中与验证相关的属性

  1. Rules:设置验证规则
  2. HideDetails:隐藏提示和验证错误等信息。true/false/auto,当设置为 auto 时,只有在有信息要显示时,才会显示信息。
  3. Error:手动设置验证不通过
  4. ErrorCount:一次性显示错误信息的条数,默认只显示一条
  5. ErrorMessages:手动设置验证不通过的错误信息
  6. Success:手动设置验证通过
  7. SuccessMessages:手动设置验证通过的成功信息
  8. ValidateOnBlur:?
  9. ValueExpression:?


posted @ 2023-03-12 19:36  functionMC  阅读(539)  评论(0编辑  收藏  举报