时间如流水,只能流去不流回!

点赞再看,养成习惯,这是您给我创作的动力!

本文 Dotnet9 https://dotnet9.com 已收录,站长乐于分享dotnet相关技术,比如Winform、WPF、ASP.NET Core等,亦有C++桌面相关的Qt Quick和Qt Widgets等,只分享自己熟悉的、自己会的。

一、简介

介绍FluentValidation的文章不少,零度编程的介绍我引用下:FluentValidation 是一个基于 .NET 开发的验证框架,开源免费,而且优雅,支持链式操作,易于理解,功能完善,还是可与 MVC5、WebApi2 和 ASP.NET CORE 深度集成,组件内提供十几种常用验证器,可扩展性好,支持自定义验证器,支持本地化多语言。

其实它也可以用于WPF属性验证,本文主要也是讲解该组件在WPF中的使用,FluentValidation官网是: https://fluentvalidation.net/ 。

二、本文需要实现的功能

提供WPF界面输入验证,采用MVVM方式,需要以下功能:

  1. 能验证ViewModel中定义的简单属性;
  2. 能验证ViewModel中定义的复杂属性,比如对象属性的子属性,如VM有个学生属性Student,需要验证他的姓名、年龄等;
  3. 能简单提供两种验证样式;
  4. 没有了,就是前面3点…

先看实现效果图:

三、调研中遇到的问题

简单属性:验证ViewModel的普通属性比较简单,可以参考FluentValidation官网:链接 ,或者国外holymoo大神的代码: UserValidator.cs 。

复杂属性:我遇到的问题是,怎么验证ViewModel中对象属性的子属性?见第二个功能描述,FluentValidation的官网有Complex Properties的例子,但是我试了没效果,贴上官方源码截图:

最后我Google到这篇文章,根据该链接代码,ViewModel和子属性都实现IDataErrorInfo接口,即可实现复杂属性验证,文章中没有具体实现,但灵感是从这来的,就不具体说该链接代码了,有兴趣的读者可以点击链接阅读,下面说说博主自己的研发步骤(主要就是贴代码啦,您可以直接拉到文章末尾,那里赋有源码下载链接)。

四、开发步骤

4.1、创建工程、引入库

创建.Net Core WPF模板解决方案(.Net Framework模板也行):WpfFluentValidation,引入Nuget包FluentValidation(8.5.1)。

4.2、创建测试实体类学生:Student.cs

此类用作ViewModel中的复杂属性使用,学生类包含3个属性:名字、年龄、邮政编码。此实体需要继承IDataErrorInfo接口,这是触发FluentValidation验证的关键接口实现。

using System.ComponentModel;
using System.Linq;
using WpfFluentValidation.Validators; namespace WpfFluentValidation.Models
{
/// <summary>
/// 学生实体
/// 继承BaseClasss,即继承属性变化接口INotifyPropertyChanged
/// 实现IDataErrorInfo接口,用于FluentValidation验证,必须实现此接口
/// </summary>
public class Student : BaseClass, IDataErrorInfo
{
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
private int age;
public int Age
{
get { return age; }
set
{
if (value != age)
{
age = value;
OnPropertyChanged(nameof(Age));
}
}
}
private string zip;
public string Zip
{
get { return zip; }
set
{
if (value != zip)
{
zip = value;
OnPropertyChanged(nameof(Zip));
}
}
} public string Error { get; set; } public string this[string columnName]
{
get
{
if (validator == null)
{
validator = new StudentValidator();
}
var firstOrDefault = validator.Validate(this)
.Errors.FirstOrDefault(lol => lol.PropertyName == columnName);
return firstOrDefault?.ErrorMessage;
}
} private StudentValidator validator { get; set; }
}
}

4.3、创建学生验证器:StudentValidator.cs

验证属性的写法有两种:

  1. 可以在实体属性上方添加特性(本文不作特别说明,百度文章介绍很多);
  2. 通过代码的形式添加,如下方,创建一个验证器类,继承自AbstractValidator,在此验证器构造函数中写规则验证属性,方便管理。

本文使用第二种,见下方学生验证器代码:

using FluentValidation;
using System.Text.RegularExpressions;
using WpfFluentValidation.Models; namespace WpfFluentValidation.Validators
{
public class StudentValidator : AbstractValidator<Student>
{
public StudentValidator()
{
RuleFor(vm => vm.Name)
.NotEmpty()
.WithMessage("请输入学生姓名!")
.Length(, )
.WithMessage("学生姓名长度限制在5到30个字符之间!"); RuleFor(vm => vm.Age)
.GreaterThanOrEqualTo()
.WithMessage("学生年龄为整数!")
.ExclusiveBetween(, )
.WithMessage($"请正确输入学生年龄(10-150)"); RuleFor(vm => vm.Zip)
.NotEmpty()
.WithMessage("邮政编码不能为空!")
.Must(BeAValidZip)
.WithMessage("邮政编码由六位数字组成。");
} private static bool BeAValidZip(string zip)
{
if (!string.IsNullOrEmpty(zip))
{
var regex = new Regex(@"\d{6}");
return regex.IsMatch(zip);
}
return false;
}
}
}

4.4、 创建ViewModel类:StudentViewModel.cs

StudentViewModel与Student实体类结构类似,都需要实现IDataErrorInfo接口,该类由一个简单的string属性(Title)和一个复杂的Student对象属性(CurrentStudent)组成,代码如下:

using System;
using System.ComponentModel;
using System.Linq;
using WpfFluentValidation.Models;
using WpfFluentValidation.Validators; namespace WpfFluentValidation.ViewModels
{
/// <summary>
/// 视图ViewModel
/// 继承BaseClasss,即继承属性变化接口INotifyPropertyChanged
/// 实现IDataErrorInfo接口,用于FluentValidation验证,必须实现此接口
/// </summary>
public class StudentViewModel : BaseClass, IDataErrorInfo
{
private string title;
public string Title
{
get { return title; }
set
{
if (value != title)
{
title = value;
OnPropertyChanged(nameof(Title));
}
}
} private Student currentStudent;
public Student CurrentStudent
{
get { return currentStudent; }
set
{
if (value != currentStudent)
{
currentStudent = value;
OnPropertyChanged(nameof(CurrentStudent));
}
}
} public StudentViewModel()
{
CurrentStudent = new Student()
{
Name = "李刚的儿",
Age =
};
} public string this[string columnName]
{
get
{
if (validator == null)
{
validator = new ViewModelValidator();
}
var firstOrDefault = validator.Validate(this)
.Errors.FirstOrDefault(lol => lol.PropertyName == columnName);
return firstOrDefault?.ErrorMessage;
}
}
public string Error
{
get
{
var results = validator.Validate(this);
if (results != null && results.Errors.Any())
{
var errors = string.Join(Environment.NewLine, results.Errors.Select(x => x.ErrorMessage).ToArray());
return errors;
} return string.Empty;
}
} private ViewModelValidator validator;
}
}

仔细看上方代码,对比Student.cs,重写自IDataErrorInfo接口定义的Error属性与定义有所不同。Student.cs对Error基本未做修改,而StudentViewModel.cs有变化,get器中验证属性(简单属性Title和复杂属性CurrentStudent),返回错误提示字符串,诶,CurrentStudent的验证器怎么生效的?有兴趣的读者可以研究FluentValidation库源码一探究竟,博主表示研究源码其乐无穷,一时研究一时爽,一直研究一直爽。

4.5 StudentViewModel的验证器ViewModelValidator.cs

ViewModel的验证器,相比Student的验证器StudentValidator,就简单的多了,因为只需要编写验证一个简单属性Title的代码。而复杂属性CurrentStudent的验证器StudentValidator,将被WPF属性系统自动调用,即在StudentViewModel的索引器this[string columnName]和Error属性中调用,界面触发规则时自动调用。

using FluentValidation;
using WpfFluentValidation.ViewModels; namespace WpfFluentValidation.Validators
{
public class ViewModelValidator:AbstractValidator<StudentViewModel>
{
public ViewModelValidator()
{
RuleFor(vm => vm.Title)
.NotEmpty()
.WithMessage("标题长度不能为空!")
.Length(, )
.WithMessage("标题长度限制在5到30个字符之间!");
}
}
}

4.6 辅助类BaseClass.cs

简单封装INotifyPropertyChanged接口

using System.ComponentModel;

namespace WpfFluentValidation
{
public class BaseClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

4.7 、视图StudentView.xaml

用户直接接触的视图文件来了,比较简单,提供简单属性标题(Title)、复杂属性学生姓名(CurrentStudent.Name)、学生年龄( CurrentStudent .Age)、学生邮政编码( CurrentStudent .Zip)验证,xaml代码如下:

<UserControl x:Class="WpfFluentValidation.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:WpfFluentValidation.Views"
xmlns:vm="clr-namespace:WpfFluentValidation.ViewModels"
mc:Ignorable="d"
d:DesignHeight="" d:DesignWidth="">
<UserControl.DataContext>
<vm:StudentViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions> <GroupBox Header="ViewModel直接属性验证">
<StackPanel Orientation="Horizontal">
<Label Content="标题:"/>
<TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Style="{StaticResource ErrorStyle1}"/>
</StackPanel>
</GroupBox>
<GroupBox Header="ViewModel对象属性CurrentStudent的属性验证" Grid.Row="">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="姓名:"/>
<TextBox Text="{Binding CurrentStudent.Name, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Style="{StaticResource ErrorStyle2}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="年龄:"/>
<TextBox Text="{Binding CurrentStudent.Age, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Style="{StaticResource ErrorStyle2}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="邮编:" />
<TextBox Text="{Binding CurrentStudent.Zip, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Style="{StaticResource ErrorStyle2}"/>
</StackPanel>
</StackPanel>
</GroupBox>
</Grid>
</UserControl>

4.8 、错误提示样式

本文提供了两种样式,具体效果见前面的截图,代码如下:

<Application x:Class="WpfFluentValidation.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfFluentValidation"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="StackPanel">
<Setter Property="Margin" Value="0 5"/>
</Style>
<!--第一种错误样式,红色边框-->
<Style TargetType="{x:Type TextBox}" x:Key="ErrorStyle1">
<Setter Property="Width" Value=""/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Grid DockPanel.Dock="Right" Width="" Height=""
VerticalAlignment="Center" Margin="3 0 0 0">
<Ellipse Width="" Height="" Fill="Red"/>
<Ellipse Width="" Height=""
VerticalAlignment="Top" HorizontalAlignment="Center"
Margin="0 2 0 0" Fill="White"/>
<Ellipse Width="" Height="" VerticalAlignment="Bottom"
HorizontalAlignment="Center" Margin="0 0 0 2"
Fill="White"/>
</Grid>
<Border BorderBrush="Red" BorderThickness="" CornerRadius="">
<AdornedElementPlaceholder/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource=
{x:Static RelativeSource.Self},
Path=(Validation.Errors)[].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style> <!--第二种错误样式,右键文字提示-->
<Style TargetType="{x:Type TextBox}" x:Key="ErrorStyle2">
<Setter Property="Width" Value=""/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Margin="" Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource=
{x:Static RelativeSource.Self},
Path=(Validation.Errors)[].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
</Application>

五、 介绍完毕

码农就是这样,文章基本靠贴代码,哈哈。

6、源码同步

本文代码已同步gitee: https://gitee.com/lsq6/FluentValidationForWpf

github: https://github.com/dotnet9/FluentValidationForWPF

CSDN: https://download.csdn.net/download/HenryMoore/11984265

《Dotnet9》系列-FluentValidation在C# WPF中的应用的更多相关文章

  1. FluentValidation在C# WPF中的应用

    原文:FluentValidation在C# WPF中的应用 一.简介 介绍FluentValidation的文章不少,零度编程的介绍我引用下:FluentValidation 是一个基于 .NET ...

  2. WPF Step By Step 系列-Prism框架在项目中使用

    WPF Step By Step 系列-Prism框架在项目中使用 回顾 上一篇,我们介绍了关于控件模板的用法,本节我们将继续说明WPF更加实用的内容,在大型的项目中如何使用Prism框架,并给予Pr ...

  3. CleanAOP实战系列--WPF中MVVM自动更新

    CleanAOP实战系列--WPF中MVVM自动更新 作者: 立地 邮箱: jarvin_g@126.com QQ: 511363759 CleanAOP介绍:https://github.com/J ...

  4. 浏览器扩展系列————在WPF中定制WebBrowser快捷菜单

    原文:浏览器扩展系列----在WPF中定制WebBrowser快捷菜单 关于如何定制菜单可以参考codeproject上的这篇文章:http://www.codeproject.com/KB/book ...

  5. WPF中的常用布局 栈的实现 一个关于素数的神奇性质 C# defualt关键字默认值用法 接口通俗理解 C# Json序列化和反序列化 ASP.NET CORE系列【五】webapi整理以及RESTful风格化

    WPF中的常用布局   一 写在开头1.1 写在开头微软是一家伟大的公司.评价一门技术的好坏得看具体的需求,没有哪门技术是面面俱到地好,应该抛弃对微软和微软的技术的偏见. 1.2 本文内容本文主要内容 ...

  6. WPF入门教程系列十八——WPF中的数据绑定(四)

    六.排序 如果想以特定的方式对数据进行排序,可以绑定到 CollectionViewSource,而不是直接绑定到 ObjectDataProvider.CollectionViewSource 则会 ...

  7. WPF入门教程系列十五——WPF中的数据绑定(一)

    使用Windows Presentation Foundation (WPF) 可以很方便的设计出强大的用户界面,同时 WPF提供了数据绑定功能.WPF的数据绑定跟Winform与ASP.NET中的数 ...

  8. WPF中找不到Image或者Image不是Drawing系列

    WPF中默认没有引用WinForm里面的一些东西,都是用它自带的那一套,但又不能完全脱离,所以有的时候比较蛋疼

  9. 关于WPF中ItemsControl系列控件中Item不能继承父级的DataContext的解决办法

    WPF中所有的集合类控件,子项都不能继承父级的DataContext,需要手动将绑定的数据源指向到父级控件才可以. <DataGridTemplateColumn Header="操作 ...

随机推荐

  1. Java w3c离线手册

    提供给大家使用,懒得找: 前端后端一般都用的到  查看文档 1. JDK_API_1_6_zh_CN.CHM 2.     W3School离线手册(2018.04.01).chm 3.    jqu ...

  2. 解决failed to push some refs to 'git@github.com:TQBX/GIT-Github-.git'问题

    解决以下问题问题: git pull origin master --allow-unrelated-histories 进入vim界面->ESC->:wq 重复第一步->git p ...

  3. 【2018寒假集训 Day2】【动态规划】挖地雷

    挖地雷(Mine) 在一个地图上有N 个地窖(N<=200),每个地窖中埋有一定数量的地雷.同时,给出地窖之间的连接路径,并规定路径都是单向的,且从编号小的地窖通向编号大的地窖.某人可以从任一处 ...

  4. 【集训Day1 测试】装饰

    装饰(decorate) [题目描述] 一个图有 N 个结点,编号 1 至 N,有 M 条无向边,第 i 条边连接的两个结点是 Ai 和Bi,其中 Ai 和 Bi 是不同的结点.可能有多条边连接的是同 ...

  5. 在Kubernetes上运行有状态应用:从StatefulSet到Operator

    一开始Kubernetes只是被设计用来运行无状态应用,直到在1.5版本中才添加了StatefulSet控制器用于支持有状态应用,但它直到1.9版本才正式可用.本文将介绍有状态和无状态应用,一个通过K ...

  6. 深入了解CSS中盒子模型

    CSS中盒子模型介绍 什么是盒子? 盒子是用来存储物品,我们可以将盒子理解为酒盒,酒盒有什么组成的呢? 有酒可以喝.有填充物保护酒防止酒被摔坏.纸盒子. 我们怎么理解CSS中的盒子呢,CSS中盒子有什 ...

  7. centos7 安装wps

    # cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) # cat /proc/version Linux version 3.1 ...

  8. 虚拟化--思杰citrix

    目前虚拟化主要有vmware,微软,思杰 一:从硬件搭建开始 硬件需要问的几个问题: a.负载均衡.防火墙.路由器怎么配置? b.新增一块存储的话,怎么新增? 二:安装citrix xen serve ...

  9. cesium添加多个geojson文件并分别控制显示和隐藏

    /*获取geojson数据*/ function get_geojson(name,h,n){ let x=document.getElementById(n); if(x.className === ...

  10. 【华为云实战开发】9.如何进行PHP项目的快速搭建并实现CICD?【华为云技术分享】

    1 概述 1.1 文章目的 本文主要想为研发PHP项目的企业或个人提供上云指导,通过本文中的示例项目 “workerman-todpole”,为开发者提供包括项目管理,代码托管,代码检查,编译构建,测 ...