TrykleMvvmToolkit.Wpf 模型验证

介绍

本验证可以使用3个方式进行操作

  • 2 使用 System.ComponentModel.DataAnnotations 特性验证
  • 3 在构造函数中使用SetRule设置额外规则
  • 1 添加事件Errors.Validating可以调用其他库进行验证

这三种验证方式,按照123顺序执行,其中任意一个出现错误都将直接短路返回

使用

需要验证的 model 从 ValidationViewModelBase 继承

public class User : ValidationViewModelBase
{
    [Required(ErrorMessage = "请填写邮箱")]
    [EmailAddress(ErrorMessage = "邮箱格式错误")]
    public string Email { get => GetProperty<string>(); set => SetPropertyAndValidate(value); }

触发一个属性的验证

在属性绑定到UI时,属性改变将通知验证,如验证出现错误,验证信息将保存并通知到基类的Error属性中,可以使用Errors[属性名]来访问具体的错误信息
转换器ObjectToVisibilityConverter在此处的作用是将非空文本转换为可见性

<StackPanel Margin="10">
    <TextBox Height="26" Text="{Binding Path=User.Email, UpdateSourceTrigger=PropertyChanged}" />
    <TextBlock Foreground="Red" Text="{Binding Path=User.Errors[Email]}" 
                               Visibility="{Binding Path=User.Errors[Email],
                                Converter={t:ObjectToVisibilityConverter FalseToCollapsed=True}}" />
</StackPanel>

全部验证

一个Modal中往往有很多属性需要验证,根据上方触发验证的设计,你只能手动更改所有属性来触发所有属性验证
使用 Errors.ValidateAll() 可以一次性验证全部(这样你会看到UI上显示一堆提示)
Errors.ValidateAll() 可以用在按钮提交事件内进行验证,或者初始时使用Errors.ValidateAll()之后使用Errors.Count用于按钮启用绑定

Error.Count 中储存了错误数量
Error.Messages 中储存了错误信息字符串集合

收集常用验证规则

//字段非空,最大长度160
[Required(ErrorMessage="FirstName不能为空")]
[StringLength(160, ErrorMessage = "FirstName太长了")]
 [StringLength(32, MinimumLength = 6, ErrorMessage = "密码长度应在{2}-{1}之间")]
public string FirstName { get; set; }
//正则表达式验证必须是电子邮件
[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}",
    ErrorMessage="邮件地址不正确")]
public string Email { get; set; } 
//字段非空,数值类型最小值,最大值
[Required]
[Range(typeof(decimal), "0.00", "49.99")]
public decimal Total { get; set; }
//对象两个属性拥有相同的值
[Compare("Email")]
public string EmailConfirm { get; set; } 

使用SetRule方法设置验证规则

SetRule(() => Email, () => Email?[0] == 'A', "邮箱第一个字母必须以A开头");

用抛出异常的方式传递错误消息

SetRule(() => Email, () =>
{
    ThrowErrorIf(Email.Length != 12, $"邮箱长度必须12,当前({Email?.Length})");
});

使用Validating事件设置验证规则

Errors.Validating += e =>
{
    if (e.Name == nameof(Email))
    {
        if ($"{e.Input}".Length > 10)
        {
            e.Failed("邮箱长度要小于10,如果有其他验证的库需要接入,可以在这里使用ReturnError中断内部验证并返回失败");
        }
    }
};

补充

通过给Errors对象添加Validating事件,可以定制额外的拦截步骤
通过给Errors对象添加Validated事件,可以获得验证后的回调
注意:Validated事件内,不要对验证的属性赋值,将造成调用溢出(已修改为抛出异常作为拦截)

数字属性绑定到字符串属性的弊端和解决方案

前台Binding的错误无法传播到模型来,也就无法进行验证,
比如TextBox上绑定int类型的属性,在输入的文本无法转换为int时,会触发绑定出错,值不会传递到模型中
你可以通过限制控件本身的输入来将一个默认值传递到模型,这样便会触发验证
或者,使用一个转换器来阻止默认的绑定报错
FallbackValue可设置转换错误时返回的兜底值

<TextBox Height="26" Text="{Binding User.Age, Converter={t:StringToNumberConverter FallbackValue=-1}}" />

单独使用ValidationObject

对于不能继承ValidationViewModelBase的情况,可以单独使用

public class Person
{
    [Required(ErrorMessage = "名字不能为空")]
    public string Name { get; set; }

    [Range(typeof(int), "1", "99", ErrorMessage = "年龄必须要在{1}和{2}之间")]
    public int Age { get; set; }
}

internal class Program
{
    [STAThread]
    static void Main(string[] args)
{
    var p = new Person();
    var vo = new ValidationObject<Person>(p);

    //额外规则添加
    vo.AddRule(x => x.Age, x => x.Age < 9, "年龄必须大于9岁");
    vo.AddRule(x => x.Name, x => vo.ThrowErrorIf(string.IsNullOrEmpty(x.Name), "名字不可为空"));

    vo.AddRule(
       x => x.Name,
       x =>
       {
           return string.IsNullOrWhiteSpace(x.Name); 
       },
       "请填写Name"
   );


    //验证全部
    vo.ValidateAll();

    Console.WriteLine("Count:" + vo.Count);
    foreach (var item in vo.Messages)
    {
        Console.WriteLine(item);
    }

    //验证部分
    if (!vo.Validate(nameof(Person.Name), out var errMsg))
    {
        Console.WriteLine($"-->{errMsg}");
    }

    Console.Read();
}
}
posted @ 2022-05-30 11:27  trykle  阅读(53)  评论(0)    收藏  举报