1. 引言
在.NET开发领域,FluentValidation以其优雅、易扩展的特性成为开发者进行属性验证的首选工具。它不仅适用于Web开发,如MVC、Web API和ASP.NET CORE,同样也能完美集成在WPF应用程序中,提供强大的数据验证功能。本文将深入探讨如何在C# WPF项目中运用FluentValidation进行属性验证,并展示如何通过MVVM模式实现这一功能。
2. 功能概览
我们的目标是构建一个WPF应用程序,它能够通过FluentValidation实现以下验证功能:
- 验证ViewModel层的基本数据类型属性,如int、string等。
- 对ViewModel中的复杂属性进行验证,这包括对象属性的子属性以及集合属性。
- 提供两种直观的错误提示样式,以增强用户体验。
先看实现效果图:

3. 解决问题与探索
在调研过程中,我发现FluentValidation官方文档主要关注于Web应用的验证。对于WPF和复杂属性的验证,官方文档提供的示例有限。然而,通过深入研究和实践,我找到了将FluentValidation与WPF结合使用的有效方法,特别是针对复杂属性的验证。
4. 开发步骤
4.1. 创建工程、库引入
首先,创建一个新的WPF项目,并引入FluentValidation库用于属性验证,以及Prism.Wpf库以简化MVVM模式的实现。
<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.9.0" />
<PackageReference Include="Prism.Wpf" Version="9.0.271-pre" />
</ItemGroup>
4.2. 创建实体类
我创建了两个实体类:Student和Field,分别代表对象属性和集合项属性。这两个类都实现了IDataErrorInfo接口:
IDataErrorInfo接口常用于提供实体数据验证的错误信息。这个接口包含两个成员:一个索引器(this[string columnName])和一个Error属性。索引器用于按属性名称提供错误信息,而Error属性则用于提供整个实体的错误概述。- 两个实体类和另外在后面提及的ViewModel中也实现
IDataErrorInfo接口,并在this[string columnName]索引器和Error属性中使用FluentValidation来验证属性。
4.2.1. 普通类 - Student
学生类包含5个属性:名字、年龄、邮政编码、最小值和最大值,其中最小值和最大值涉及关联验证,即最小值变化后通知最大值验证,反之同理。
/// <summary>
/// 学生实体
/// 继承BindableBase,即继承属性变化接口INotifyPropertyChanged
/// 实现IDataErrorInfo接口,用于FluentValidation验证,必须实现此接口
/// </summary>
public class Student : BindableBase, IDataErrorInfo
{
private int _age;
private string? _name;
private string? _zip;
private readonly StudentValidator _validator = new();
public string? Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public int Age
{
get => _age;
set => SetProperty(ref _age, value);
}
public string? Zip
{
get => _zip;
set => SetProperty(ref _zip, value);
}
private int _minValue;
public int MinValue
{
get => _minValue;
set
{
SetProperty(ref _minValue, value);
// 关联更新最大值验证
RaisePropertyChanged(nameof(MaxValue));
}
}
private int _maxValue;
public int MaxValue
{
get => _maxValue;
set
{
SetProperty(ref _maxValue, value);
// 关联更新最小值验证
RaisePropertyChanged(nameof(MinValue));
}
}
public string this[string columnName]
{
get
{
var validateResult = _validator.Validate(this);
if (validateResult.IsValid)
{
return string.Empty;
}
var firstOrDefault =
validateResult.Errors.FirstOrDefault(error => error.PropertyName == columnName);
return firstOrDefault == null ? string.Empty : firstOrDefault.ErrorMessage;
}
}
public string Error
{
get
{
var validateResult = _validator.Validate(this);
if (validateResult.IsValid)
{
return string.Empty;
}
var errors = string.Join(Environment.NewLine, validateResult.Errors.Select(x => x.ErrorMessage).ToArray());
return errors;
}
}
}
上面关键代码在public string this[string columnName]:这里进行输入表单项的数据校验,FluentValidation调用就在这里,校验逻辑封装在StudentValidator,表单输入时会实时调用该处代码,columnName表示表单项的列名,就是View绑定的属性名。
4.2.2. 集合类 - Field
此类用作ViewModel中的集合项使用,模拟动态表单数据校验,简单包含4个属性:字段名称、字段显示名称、数据类型、数据值,表单主要根据数据类型验证输入的数据值是否合法。同样此实体需要继承IDataErrorInfo接口,用于触发FluentValidation验证使用。
/// <summary>
/// 扩展字段,用于生成动态表单
/// 继承BindableBase,即继承属性变化接口INotifyPropertyChanged
/// 实现IDataErrorInfo接口,用于FluentValidation验证,必须实现此接口
/// </summary>
public class Field : BindableBase, IDataErrorInfo
{
private string? _value;
private readonly FieldValidator _validator = new();
public Field(DataType type, string typeLabel, string name, string value)
{
Type = type;
TypeLabel = typeLabel;
Name = name;
Value = value;
}
/// <summary>
/// 数据类型
/// </summary>
public DataType Type {
get; set; }
/// <summary>
/// 数据类型名称
/// </summary>
public string TypeLabel {
get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name {
get; set; }
/// <summary>
/// 值
/// </summary>
public string? Value
{
get => _value;
set => SetProperty(ref _value, value);
}
public string this[string columnName]
{
get
{
var validateResult = _validator.Validate(this);
if (validateResult.IsValid)
{
return string.Empty;
}
var firstOrDefault =
validateResult.Errors.FirstOrDefault(error => error.PropertyName == columnName);
return firstOrDefault == null ? string.Empty : firstOrDefault.ErrorMessage;
}
}
public string Error
{
get
{
var validateResult = _validator.Validate(this);
if (validateResult.IsValid)
{
return string.Empty;
}
var errors = string.Join(Environment.NewLine, validateResult.Errors.Select(x => x.ErrorMessage).ToArray());
return errors;
}
}
}
public enum DataType
{
Text,
Number,
Date
}
看上面代码,public string this[string columnName]代码处写法和Student类一样,只是_validator变量类型不同,前者为StudentValidator,这里是FieldValidator

1732

被折叠的 条评论
为什么被折叠?



