WPF使用FluentValidation进行表单验证
WPF使用FluentValidation进行表单验证
使用.net6.0
使用的NuGet包
FluentValidation:11.6.0
MaterialDesignThemes:4.9.0
Prism.DryIoc:8.1.97
在WPF里验证表单使用的是INotifyDataErrorInfo接口,这个接口长这样
public interface INotifyDataErrorInfo
{
bool HasErrors { get; }
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
IEnumerable GetErrors(string propertyName);
}
HasErrors:用于判断是否有错误ErrorsChanged:用于通知View刷新界面GetErrors:用于获取属性的错误信息
实现INotifyDataErrorInfo
定义一个抽象类ValidatableBindableBase,继承BindableBase,并实现INotifyDataErrorInfo,有表单验证的viewmodel继承这个类就好了,在viewmodel中实现ValidateAllProperty函数
public abstract class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo
{
/// <summary>
/// 错误字典中有数据则为true
/// </summary>
public bool HasErrors
{
get
{
return this._errorDic.Any(x => null != x.Value && x.Value.Count > 0);
}
}
/// <summary>
/// 错误字典变化时触发
/// </summary>
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { return; };
/// <summary>
/// 通过属性获取错误集合
/// </summary>
/// <param name="propertyName"></param>
/// <returns></returns>
public IEnumerable GetErrors(string propertyName)
{
if (true == string.IsNullOrWhiteSpace(propertyName) || false == this._errorDic.ContainsKey(propertyName))
{
return null;
}
return this._errorDic[propertyName];
}
/// <summary>
/// 错误字典
/// </summary>
private readonly Dictionary<string, List<string>> _errorDic = new Dictionary<string, List<string>>();
/// <summary>
/// 将错误添加到错误字典中
/// 通知UI刷新
/// </summary>
/// <param name="propertyName"></param>
/// <param name="errorMessage"></param>
public void SetError(string propertyName, string errorMessage)
{
if (false == this._errorDic.ContainsKey(propertyName))
{
this._errorDic.Add(propertyName, new List<string>() { errorMessage });
}
else
{
//其实这步多余,不需要额外的错误信息
this._errorDic[propertyName].Add(errorMessage);
}
this.RaiseErrorChanged(propertyName);
}
/// <summary>
/// 从错误字典中移除错误
/// 通知UI刷新
/// </summary>
/// <param name="propertyName"></param>
protected void ClearError(string propertyName)
{
if (true == this._errorDic.ContainsKey(propertyName))
{
this._errorDic.Remove(propertyName);
}
this.RaiseErrorChanged(propertyName);
}
/// <summary>
/// 从错误字典移除所有错误
/// </summary>
protected void ClearAllError()
{
var propertyList = this._errorDic.Select(x => x.Key).ToList();
foreach (var property in propertyList)
{
this.ClearError(property);
}
}
/// <summary>
/// 通知UI刷新
/// </summary>
/// <param name="propertyName"></param>
public void RaiseErrorChanged(string propertyName)
{
this.ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
/// <summary>
/// 验证所有属性
/// 由于验证用的validator需要子类创建,所以该函数需要子类实现
/// </summary>
public abstract void ValidateAllProperty();
/// <summary>
/// 验证属性
/// 由于验证用的validator需要子类创建,所以该函数需要子类实现
/// </summary>
/// <param name="propertyName"></param>
public abstract void ValidateProperty(string propertyName);
}
案例
稍微写的全面一点,那就写个一般属性、复杂属性、集合属性验证
先定义一个类作为复杂属性吧
public class UserModel
{
public string Username { get; set; }
public string Nickname { get; set; }
}
因为要验证这个复杂属性,所以要再定义一个Validator
public class UserModelValidator : AbstractValidator<UserModel>
{
public UserModelValidator()
{
this.RuleFor(x => x.Username)
.NotNull()
.NotEmpty()
.WithMessage("用户名不能为空");
this.RuleFor(x => x.Nickname)
.NotNull()
.WithMessage("昵称不能为NULL")
.NotEmpty()
.WithMessage("昵称不能为空");
}
}
然后定义一个ViewModel类,这个就是表单
public class FormViewModel : ValidatableBindableBase
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
this.RaisePropertyChanged(nameof(FirstName));
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
this.RaisePropertyChanged(nameof(LastName));
}
}
private List<string> _nameList;
public List<string> NameList
{
get { return _nameList; }
set
{
_nameList = value;
this.RaisePropertyChanged(nameof(NameList));
}
}
private UserModel _userModel;
public UserModel UserModel
{
get { return _userModel; }
set
{
_userModel = value;
this.RaisePropertyChanged(nameof(UserModel));
}
}
private List<UserModel> _userList;
public List<UserModel> UserList
{
get { return _userList; }
set
{
_userList = value;
this.RaisePropertyChanged(nameof(UserList));
}
}
public FormViewModel()
{
this.FirstName = string.Empty;
this.LastName = string.Empty;
this.NameList = new List<string>();
this.UserModel = new UserModel();
this.UserList = new List<UserModel>();
}
}
再定义ViewModel的Validator
public class FormViewModelValidator : AbstractValidator<FormViewModel>
{
public FormViewModelValidator()
{
//验证一般属性
this.RuleFor(x => x.FirstName)
.NotNull()
.NotEmpty()
.WithMessage("FirstName不能为空");
this.RuleFor(x => x.LastName)
.NotNull()
.WithMessage("LastName不能为NULL")
.NotEmpty()
.WithMessage("LastName不能为空");
//验证集合属性,对集合中每个元素进行验证
this.RuleForEach(x => x.NameList)
.NotNull()
.NotEmpty()
.WithMessage("NameList不能为空");
//验证复杂属性
this.RuleFor(x => x.UserModel)
.SetValidator(new UserModelValidator());
//验证复杂集合属性
this.RuleForEach(x => x.UserList)
.SetValidator(new UserModelValidator());
}
}
Validator
Validator要继承AbstractValidator<T>,在构造函数中定义验证规则,本地化也可以在这里使用
一般属性
这个比较简单,直接加规则就可以,可以链式调用
此时_errorDic的key为属性名称
注意,如果使用链式调用,错误消息可以是共享的,也可以是单独的
public FormViewModelValidator()
{
this.RuleFor(x => x.FirstName)
.NotNull()
.NotEmpty()
.WithMessage("FirstName不能为空");
this.RuleFor(x => x.LastName)
.NotNull()
.WithMessage("LastName不能为NULL")
.NotEmpty()
.WithMessage("LastName不能为空");
}
也可以分开写,下面两个写法是一样的
public FormViewModelValidator()
{
this.RuleFor(x => x.LastName)
.NotNull()
.WithMessage("LastName不能为NULL")
.NotEmpty()
.WithMessage("LastName不能为空");
this.RuleFor(x => x.LastName)
.NotNull()
.WithMessage("LastName不能为NULL");
this.RuleFor(x => x.LastName)
.NotEmpty()
.WithMessage("LastName不能为空");
}
复杂属性
其实复杂属性不应该在MVVM中存在,这不符合设计,不过来都来了,就顺便写了
此时_errorDic的key为复杂属性名称.复杂属性的属性名称
public FormViewModelValidator()
{
this.RuleFor(x => x.UserModel)
.SetValidator(new UserModelValidator());
}
集合属性
此时_errorDic的key为复杂集合属性名称[索引]
public FormViewModelValidator()
{
this.RuleForEach(x => x.NameList)
.NotNull()
.NotEmpty()
.WithMessage("NameList不能为空");
}
复杂集合属性
类似复杂属性
此时_errorDic的key为复杂集合属性名称[索引].复杂属性的属性名称
public FormViewModelValidator()
{
this.RuleForEach(x => x.UserList)
.SetValidator(new UserModelValidator());
}
旧版写法,SetCollectionValidator已弃用
public FormViewModelValidator()
{
this.RuleFor(x => x.UserList)
.SetCollectionValidator(new UserModelValidator());
}
规则集
如果不指定使用规则集校验,则不会使用
此时_errorDic的key也和上面的一样,因为规则集只是规则的集合,并不影响校验结果的key
public FormViewModelValidator()
{
this.RuleSet("TestRuleSet", () =>
{
this.RuleFor(x => x.FirstName)
.NotNull()
.NotEmpty()
.WithMessage("FirstName不能为空");
this.RuleFor(x => x.LastName)
.NotNull()
.WithMessage("LastName不能为NULL")
.NotEmpty()
.WithMessage("LastName不能为空");
});
}
验证方法
在ViewModel中添加Validator并实现ValidateAllProperty方法,验证结果判断HasErrors就可以了
{
private readonly FormViewModelValidator _validator;
public FormViewModel()
{
this._validator = new FormViewModelValidator();
//初始化移除所有错误
this.ClearAllError();
}
public override void ValidateAllProperty()
{
//先移除所有错误
this.ClearAllError();
var result = this._validator.Validate(this);
//添加错误
foreach (var error in result.Errors)
{
this.SetError(error.PropertyName, error.ErrorMessage);
}
}
public override void ValidateProperty(string propertyName)
{
//先移除错误
this.ClearError(propertyName);
var result = this._validator.Validate(this, (option) =>
{
option.IncludeProperties(propertyName);
});
//添加错误
foreach (var error in result.Errors)
{
this.SetError(error.PropertyName, error.ErrorMessage);
}
}
}
验证所有规则
public override void ValidateAllProperty()
{
this._validator.Validate(this);
}
验证指定属性
public override void ValidateProperty()
{
this._validator.Validate(this, (option) =>
{
option.IncludeProperties(x => x.FirstName);
option.IncludeProperties(x => x.LastName);
});
}
验证规则集
这是params参数
this._validator.Validate(this, (option) =>
{
option.IncludeRuleSets("TestRuleSet1", "TestRuleSet2");
});
也可以使用IncludeAllRuleSets,这个会包括所有规则和规则集
验证规则
这些是FluentValidation自带的验证规则
NullNotNullEmptyNotEmptyLengthMaximumLengthMinimumLengthMatchesEmailAddressNotEqualEqualLessThanLessThanOrEqualToGreaterThanGreaterThanOrEqualToInclusiveBetweenExclusiveBetweenCreditCardIsInEnumIsEnumNameScalePrecisionPrecisionScaleMustCustom
Must和Custom可以自定义规则,Must自定义的是表达式,Custom是自定义规则+错误提示
界面
注意:要启用错误消息,需要在绑定数据时添加ValidatesOnDataErrors=True
MainView,主窗口,加一个区域
<Window
x:Class="BlankApp1.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:prism="http://prismlibrary.com/"
xmlns:viewmodels="clr-namespace:BlankApp1.ViewModels"
Title="{Binding Title}"
Width="500"
Height="600"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<ContentControl prism:RegionManager.RegionName="{Binding FormRegionName}" />
</Grid>
</Window>
MainViewModel,主窗口ViewModel,导航
public class MainViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _formRegionName;
public string FormRegionName
{
get { return _formRegionName; }
set
{
_formRegionName = value;
this.RaisePropertyChanged(nameof(FormRegionName));
}
}
public MainViewModel(IRegionManager regionManager)
{
this._regionManager = regionManager;
this.Title = "测试";
this.FormRegionName = "FormRegion";
this._regionManager.RegisterViewWithRegion(this.FormRegionName, typeof(FormView));
}
}
FormView,表单控件,绑定数据记得添加ValidatesOnDataErrors=True,这里我就验证简单的数据,只演示验证结果,不想搞太复杂的东西
<UserControl
x:Class="BlankApp1.Views.FormView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BlankApp1.Views"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
d:DesignHeight="600"
d:DesignWidth="500"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="0,0,10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="FirstName" />
<TextBox
Grid.Row="0"
Grid.Column="1"
Width="200"
Margin="10,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
materialDesign:TextFieldAssist.HasClearButton="True"
Style="{StaticResource MaterialDesignFilledTextBox}"
Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="0,0,10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="LastName" />
<TextBox
Grid.Row="1"
Grid.Column="1"
Width="200"
Margin="10,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
materialDesign:TextFieldAssist.HasClearButton="True"
Style="{StaticResource MaterialDesignFilledTextBox}"
Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
<Button
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Width="80"
Command="{Binding ValidateCommand}"
Content="验证" />
</Grid>
</UserControl>
FormViewModel,这里验证表单
public class FormViewModel : ValidatableBindableBase
{
private readonly FormViewModelValidator _validator;
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
this.ValidateProperty(nameof(FirstName));
this.RaisePropertyChanged(nameof(FirstName));
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
this.ValidateProperty(nameof(LastName));
this.RaisePropertyChanged(nameof(LastName));
}
}
public DelegateCommand ValidateCommand { get; set; }
public FormViewModel()
{
this._validator = new FormViewModelValidator();
this.FirstName = string.Empty;
this.LastName = string.Empty;
this.ValidateCommand = new DelegateCommand(this.ValidateCommandExecute);
//初始化移除所有错误
this.ClearAllError();
}
public void ValidateCommandExecute()
{
this.ValidateAllProperty();
if (true == this.HasErrors)
{
return;
}
}
public override void ValidateAllProperty()
{
//先移除所有错误
this.ClearAllError();
var result = this._validator.Validate(this);
//添加错误
foreach (var error in result.Errors)
{
this.SetError(error.PropertyName, error.ErrorMessage);
}
}
public override void ValidateProperty(string propertyName)
{
//先移除错误
this.ClearError(propertyName);
var result = this._validator.Validate(this, (option) =>
{
option.IncludeProperties(propertyName);
});
//添加错误
foreach (var error in result.Errors)
{
this.SetError(error.PropertyName, error.ErrorMessage);
}
}
}
FormViewModelValidator,表单验证规则
public class FormViewModelValidator : AbstractValidator<FormViewModel>
{
public FormViewModelValidator()
{
this.RuleFor(x => x.FirstName)
.NotNull()
.NotEmpty()
.WithMessage("FirstName不能为空")
.NotEqual("123")
.WithMessage("不能等于123");
this.RuleFor(x => x.LastName)
.NotNull()
.WithMessage("LastName不能为NULL")
.NotEmpty()
.WithMessage("LastName不能为空");
}
}
效果

WPF使用FluentValidation进行表单验证 结束
有个小问题,绑定数据要到控件失去焦点才会改动ViewModel的数据
还有就是要注意控件的一键清除功能,比如material design的控件HasClearButton,有null和空字符串问题

浙公网安备 33010602011771号