FluentValidation在C# WPF中的应用
一、简介
介绍fluentvalidation的文章不少,的介绍我引用下:fluentvalidation 是一个基于 .net 开发的验证框架,开源免费,而且优雅,支持链式操作,易于理解,功能完善,还是可与 mvc5、webapi2 和 asp.net core 深度集成,组件内提供十几种常用验证器,可扩展性好,支持自定义验证器,支持本地化多语言。
其实它也可以用于wpf属性验证,本文主要也是讲解该组件在wpf中的使用,fluentvalidation官网是: 。
二、本文需要实现的功能
提供wpf界面输入验证,采用mvvm方式,需要以下功能:
- 能验证viewmodel中定义的简单属性;
- 能验证viewmodel中定义的复杂属性,比如对象属性的子属性,如vm有个学生属性student,需要验证他的姓名、年龄等;
- 能简单提供两种验证样式;
- 没有了,就是前面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
验证属性的写法有两种:
- 可以在实体属性上方添加特性(本文不作特别说明,百度文章介绍很多);
- 通过代码的形式添加,如下方,创建一个验证器类,继承自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(5, 30) .withmessage("学生姓名长度限制在5到30个字符之间!"); rulefor(vm => vm.age) .greaterthanorequalto(0) .withmessage("学生年龄为整数!") .exclusivebetween(10, 150) .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 = 23 }; } 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(5, 30) .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="450" d:designwidth="800"> <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="1"> <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="200"/> <setter property="validation.errortemplate"> <setter.value> <controltemplate> <dockpanel> <grid dockpanel.dock="right" width="16" height="16" verticalalignment="center" margin="3 0 0 0"> <ellipse width="16" height="16" fill="red"/> <ellipse width="3" height="8" verticalalignment="top" horizontalalignment="center" margin="0 2 0 0" fill="white"/> <ellipse width="2" height="2" verticalalignment="bottom" horizontalalignment="center" margin="0 0 0 2" fill="white"/> </grid> <border borderbrush="red" borderthickness="2" cornerradius="2"> <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)[0].errorcontent}"/> </trigger> </style.triggers> </style> <!--第二种错误样式,右键文字提示--> <style targettype="{x:type textbox}" x:key="errorstyle2"> <setter property="width" value="200"/> <setter property="validation.errortemplate"> <setter.value> <controltemplate> <stackpanel orientation="horizontal"> <adornedelementplaceholder x:name="textbox"/> <textblock margin="10" 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)[0].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的博客博主「沙漠尽头的狼」的原创文章,遵循 cc 4.0 by-sa 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://dotnet9.com/?p=853
上一篇: Python中Pyyaml模块的使用
下一篇: 三国武将最想要的一样东西,到底是什么?