当前位置: 移动技术网 > IT编程>开发语言>.net > 《Dotnet9》系列-FluentValidation在C# WPF中的应用

《Dotnet9》系列-FluentValidation在C# WPF中的应用

2019年12月17日  | 移动技术网IT编程  | 我要评论

湖南常德裸女横尸街头,都市无极txt,hp触摸板

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

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

本文 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官网是:  。

二、本文需要实现的功能

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

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

先看实现效果图:

《dotnet9》系列-fluentvalidation在c# wpf中的应用

三、调研中遇到的问题

简单属性:验证viewmodel的普通属性比较简单,可以参考fluentvalidation官网: ,或者国外holymoo大神的代码: uservalidator.cs 。

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

《dotnet9》系列-fluentvalidation在c# wpf中的应用

最后我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(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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网