环境
vs2022+.net6.0+wpf+MVVM+EFcore6.0
MVVM验证示意图
INotifyDataErrorInfo接口功能
public interface INotifyDataErrorInfo { bool HasErrors { get; }//提供给Validation 使用,对应Validation.HasError event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; IEnumerable GetErrors(string propertyName);//获取一组验证信息, 多个 ValidationAttribute验证后返回来的信息,提供给Validation 使用,对应Validation.ErrorsBing会在绑定的控件上生成 附加属性Validation.Errors,保存验证信息。 }
1、异步验证
2、跨属性验证
3、返回一组(IEnumerable)错误信息
实现方式
1、反射 实现验证:实现Model实体映射和数据验证分离。
2、Validator验证器实现验证:Model实体映射和数据验证混合在一起
Bing在UI绑定中作用
1、属性验证,并且记录结果。
2、Bing会在绑定的控件上生成 附加属性Validation.Errors,保存验证信息。
反射实现属性验证
目的:实现实体映射和数据验证分离。
具体步骤:
1、新建(或自动生成)Student类,这个类不做修改,保存原样。该类主要实现实体映射或者知识单纯的PoCo。
2、新建Student_Matedata的元数类,将Student_Matedata类附加Student类的元数据。Student_Matedata的元数类只有ValidationAttribute 特性。
3、新建StudentViewModel,该类实现INotifyDataErrorInfo接口,实现验证功能。
4、UI层通过Bing绑定控件,Bing会在绑定的控件上生成Validation.Errors附加属性,保存验证信息。
Model
Student类 、Student_Matedata类。
1、新建(或自动生成)Student类,这个类不做修改,保存原样。该类主要实现实体映射或者知识单纯的PoCo。
2、新建Student_Matedata的元数类,将Student_Matedata类附加Student类的元数据。Student_Matedata的元数类只有ValidationAttribute 特性。
Student类
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; using CommunityToolkit.Mvvm; using CommunityToolkit.Mvvm.ComponentModel; namespace CTMvvmDemo.MVVM.Models { [MetadataType(typeof(Student_MateData))] [Table("Students")] public partial class Student:ObservableValidator { [Key]//主键 [DatabaseGeneratedAttribute(DatabaseGeneratedOption.None)]//非自增长,自增长为Identity public int StudentID { get; set; } [Column("name")] [DataType(DataType.Text)] public string Name { get; set; } [Column("age")] public int Age { get; set; } [Column("tel")] [DataType(DataType.PhoneNumber)] public string Tel { get; set; } [Column("email")] [DataType(DataType.EmailAddress)] public string Email { get; set; } } }
Student_Matedata类
using CommunityToolkit.Mvvm.ComponentModel; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel.DataAnnotations; namespace CTMvvmDemo.MVVM.Models { public class Student_MateData : ObservableValidator { [Required] public int StudentID { get; set; } [Required] [StringLength(10, ErrorMessage = "名字太长了")] public string Name { get; set; } [Required] [Range(0,200,ErrorMessage ="年龄输入不正确")] public string Age { get; set; } } }
ViewModel
新建StudentViewModel,该类实现INotifyDataErrorInfo接口,实现验证功能。
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CTMvvmDemo.MVVM.Models; using CTMvvmDemo.Respositoies; namespace CTMvvmDemo.MVVM.ViewsModels { public partial class StudentViewsModel: ViewModelBase,INotifyDataErrorInfo { private Student student ; public StudentViewsModel(Student _student) { student = _student; this.studentID = _student.StudentID; this.name = _student.Name; this.age = _student.Age; this.tel = _student.Tel; this.email = _student.Email; } private string name; public string Name { get => name; set { name = value; student.Name = value; ValidateProperty3(student,"Name"); OnPropertyChanged("Name"); } } [ObservableProperty] private int studentID; /* [ObservableProperty] private string name;*/ [ObservableProperty] private int age; [ObservableProperty] private string tel; [ObservableProperty] private string email; private readonly Dictionary<string, ICollection<string>> _validationErrors = new Dictionary<string, ICollection<string>>(); public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged; /// <summary> /// 判断是否验证 /// </summary> public bool HasErrors => _validationErrors is not null&& _validationErrors.Count>0; /// <summary> /// /// </summary> /// <param name="propertyName"></param> private void RaiseErrorsChanged(string propertyName) { if (ErrorsChanged != null) ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); } /// <summary> /// 向Bing提供错误信息。 /// </summary> /// <param name="propertyName"></param> /// <returns></returns> /// <exception cref="NotImplementedException"></exception> public IEnumerable GetErrors(string? propertyName) { return _validationErrors.ContainsKey(propertyName) ? _validationErrors[propertyName] : null; } /// <summary> /// 方法3 /// 用反射实现验证 具体步骤 /// 1、获取model类上的元数据,该元数据是独立的类通过特性附加在model上。该元数据保存着model属性的数据特性 /// 2、运行ValidationAttribute的IsValid()方法验证特性,并且返回验证信息。 /// 3、将信息保存在字典中, 该字典提供给GetErrors()使用,GetErrors()是向bing提供验证结果 /// 获取model类的上的 MetadataType 特性 中的数据特性 用于验证,这样可以实现model和model数据验证分离。 /// /// </summary> /// <param name="obj"></param> /// <param name="propertyName"></param> public void ValidateProperty3(object obj, string propertyName) { // 获取model类的上的 MetadataType 特性 , GetCustomAttributes(true)表示 是否搜索该成员的继承链以查找这些特性 Type metadatatype = obj.GetType().GetCustomAttributes(true).OfType<MetadataTypeAttribute>().First().MetadataClassType; // 在元数据上获取指定属性propertyName的元数据,如果不存该属性就return,如果存在就获取值。 PropertyInfo property = metadatatype.GetProperty(propertyName); if (property is null) return ; // 在实例上获取指定属性propertyName的属性值 object value = obj.GetType().GetProperty(propertyName).GetValue(obj, null); // 运行属性上附加特性的IsValid(IsValid) 进行验证,并且返回验证信息 v.ErrorMessage List<string> errors = (from v in property.GetCustomAttributes(true).OfType<ValidationAttribute>() where !v.IsValid(value) select v.ErrorMessage).ToList(); List<string> vs = new(); vs.Add(String.Join(",", errors)); //将指定属性 错误信息保存到字典中,该字典提供给GetErrors()使用,GetErrors()是向bing提供验证结果。 _validationErrors[propertyName] = (errors.Count > 0) ? vs : null; } protected override void Dispose(bool IsDisposed) { student = null; _validationErrors.Clear(); } } }
View
UI层通过Bing绑定控件,Bing会在绑定的控件上生成Validation.Errors附加属性,保存验证信息。
<UserControl x:Class="CTMvvmDemo.MVVM.Views.StudentView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:CTMvvmDemo.MVVM.Views" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="40"/> <RowDefinition Height="40"/> <RowDefinition Height="40"/> <RowDefinition Height="40"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right">学生名字:</TextBlock> <TextBox Grid.Row="0" Grid.Column="1" Name="nametextbox" MinWidth="200" VerticalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalAlignment="Left" Text="{Binding Name ,UpdateSourceTrigger=PropertyChanged }" > </TextBox> <ContentPresenter Grid.Row="1" Grid.Column="2" Content="{Binding ElementName=nametextbox,Path=(Validation.Errors)[0].ErrorContent}" /> </Grid> </UserControl>