Win10开发中使用 ValidationAttribute 实现数据验证

WPF 中数据验证的方式多种多样,这里就不说了。但是,在 Windows Phone 8.1 Runtime 中,要实现数据验证,只能靠最基础的手动编写条件判断代码来实现。如果用过 ASP.NET MVC 的那套数据验证的话,再来 WP8.1,那简直就是回到原始社会的感觉。

现在,得益于大一统,mobile 端的 App 也能用上 ValidationAttribute 了!(主要是指 System.ComponentModel.DataAnnotations 这个命名空间下的 Attribute)。

那么,接下来我就用 ValidationAttribute 来做一个简单的数据验证 Demo。

一、准备 Model

本次 Demo 我打算做一个用户注册的 Demo,那么用户注册的话,就需要填写 Email、Password 之类的信息,并且需要验证是否已经填写或者在正确范围内。那么我们先编写一个最基础的 Model,我就叫 User 类。

public class User
{
    public string Email
    {
        get;
        set;
    }

    public string Password
    {
        get;
        set;
    }

    public int Age
    {
        get;
        set;
    }

    public string Address
    {
        get;
        set;
    }
}

这里我准备了 4 个属性,分别是 Email、密码、年龄和地址。其中 Email、密码、年龄都是必填的,密码这种还必须有长度限制。结合 ValidationAttribute,我们修改 User 类为如下:

public class User
{
    [Required(ErrorMessage = "请填写邮箱")]
    [EmailAddress(ErrorMessage = "邮箱格式错误")]
    public string Email
    {
        get;
        set;
    }

    [Required(ErrorMessage = "请填写密码")]
    [StringLength(20, MinimumLength = 6, ErrorMessage = "密码最少 6 位,最长 20 位")]
    public string Password
    {
        get;
        set;
    }

    [Range(18, 150, ErrorMessage = "不到 18 岁不能注册,并请填写合适范围的值")]
    public int Age
    {
        get;
        set;
    }

    [StringLength(50, ErrorMessage = "地址太长")]
    public string Address
    {
        get;
        set;
    }
}

二、如何验证?

这里我们先暂停下 Demo 的编写。我们已经为属性标注好了 ValidationAttribute,那么怎么知道这个 User 类的实例是否通过了验证,而在验证不通过的时候,又是哪个属性出问题呢?既然 .Net 框架给了这些 ValidationAttribute,那么肯定也给了如何获取验证结果的。查阅 ValidationAttribute 所在的命名空间后,我们找到一个叫 Validator 的类,这个就是用户获取验证结果的。

编写测试代码:

private void GetValidationResult()
{
    User user = new User()
    {
        Email = "hello@world.com",
        Password = "123",
        Age = 18,
        Address = "XYZ"
    };

    ValidationContext context = new ValidationContext(user);
    Listresults = new List();
    bool isValid = Validator.TryValidateObject(user, context, results, true);

    Debugger.Break();
}

在这段代码中,我们构造了一个 User 类的实例,并设置了一些属性。你可以看见,其中 Password 属性是不符合验证的,因为长度不足。

接下来三行代码就是进行验证。最后是断点。

运行之后,我们可以发现,isValid 为 false,并且 results 里面被填充了一个对象。

开发中使用 ValidationAttribute 实现数据验证-validationattribute">

如果修改 Password 属性为符合验证要求的话,再次执行代码的话,那么 isValid 就会变成 true,results 的 Count 属性也会保持为 0。

所以验证的结果就是存放在 results 对象当中。

三、数据绑定与 Validation 结合

再次回到 Demo 的编写中,因为我们需要使用数据绑定,所以需要 User 类实现 INotifyPropertyChanged 接口,并且,对于验证这个需求,我们应该添加是否验证成功和验证结果这两个属性。

因为验证需求不仅仅是用在 User 类上,这里我抽象出一个基类,叫 VerifiableBase。同时我再编写一个叫 BindableBase 的基类,这个作为数据绑定模型的基础,相当于 MVVMlight 中的 ObservableObject。

BindableBase:

public abstract class BindableBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public virtual void RaisePropertyChanged([CallerMemberName]string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void Set(ref T storage, T newValue, [CallerMemberName]string propertyName = null)
    {
        if (Equals(storage, newValue))
        {
            return;
        }
        storage = newValue;
        RaisePropertyChanged(propertyName);
    }
}

VerifiableBase:

public abstract class VerifiableBase : BindableBase
{
    private VerifiableObjectErrors _errors;

    public VerifiableBase()
    {
        _errors = new VerifiableObjectErrors(this);
    }

    public VerifiableObjectErrors Errors
    {
        get
        {
            return _errors;
        }
    }

    public bool IsValid
    {
        get
        {
            return _errors.Count <= 0;
        }
    }

    public override void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        base.RaisePropertyChanged(propertyName);
        _errors = new VerifiableObjectErrors(this);
        base.RaisePropertyChanged(nameof(Errors));
        base.RaisePropertyChanged(nameof(IsValid));
    }
}

这里我添加了 IsValid 属性表示该对象是否验证成功,添加 Errors 属性存放具体的错误内容。

在属性发生变更的情况下,我们必须重新对该对象进行验证,因此 override 父类 BindableBase 中的 RaisePropertyChanged 方法,重新构建一个错误信息对象,并通知 UI 这两个属性发生了变化。

VerifiableObjectErrors:

public class VerifiableObjectErrors : IReadOnlyList
{
    private List_messages = new List();

    private List_results = new List();

    internal VerifiableObjectErrors(VerifiableBase verifiableBase)
    {
        ValidationContext context = new ValidationContext(verifiableBase);
        Validator.TryValidateObject(verifiableBase, context, _results, true);
        foreach (var result in _results)
        {
            _messages.Add(result.ErrorMessage);
        }
    }

    public int Count
    {
        get
        {
            return _messages.Count;
        }
    }

    public string this[int index]
    {
        get
        {
            return _messages[index];
        }
    }

    public string this[string propertyName]
    {
        get
        {
            foreach (var result in _results)
            {
                if (result.MemberNames.Contains(propertyName))
                {
                    return result.ErrorMessage;
                }
            }
            return null;
        }
    }

    public IEnumeratorGetEnumerator()
    {
        return _messages.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

这个对象是一个只读集合(实现 IReadOnlyList接口)。在构造函数中,验证 VerifiableBase 对象,并将验证结果存储起来。添加了一个参数类型为 string 类型的索引器,可以通过传递属性名称获取该属性的第一条错误消息,如果该属性验证通过,没有错误的话,则返回 null。

 

在这些基础的都编写完之后,修改最开始的 User 对象:

public class User : VerifiableBase
{
    private string _email;

    private string _password;

    private int _age;

    private string _address;

    [Required(ErrorMessage = "请填写邮箱")]
    [EmailAddress(ErrorMessage = "邮箱格式错误")]
    public string Email
    {
        get
        {
            return _email;
        }
        set
        {
            Set(ref _email, value);
        }
    }

    [Required(ErrorMessage = "请填写密码")]
    [StringLength(20, MinimumLength = 6, ErrorMessage = "密码最少 6 位,最长 20 位")]
    public string Password
    {
        get
        {
            return _password;
        }
        set
        {
            Set(ref _password, value);
        }
    }

    [Range(18, 150, ErrorMessage = "不到 18 岁不能注册,并请填写合适访问的值")]
    public int Age
    {
        get
        {
            return _age;
        }
        set
        {
            Set(ref _age, value);
        }
    }

    [StringLength(50, ErrorMessage = "地址太长")]
    public string Address
    {
        get
        {
            return _address;
        }
        set
        {
            Set(ref _address, value);
        }
    }
}

四、在 UI 中显示验证

测试页面我就叫 MainView,它的 ViewModel 则为 MainViewModel。

编写 MainViewModel:

public class MainViewModel
{
    private RelayCommand _registerCommand;

    private User _user;

    public MainViewModel()
    {
        _user = new User();
    }

    public RelayCommand RegisterCommand
    {
        get
        {
            _registerCommand = _registerCommand ?? new RelayCommand(async () =>
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine($"邮箱:{User.Email}");
                sb.AppendLine($"密码:{User.Password}");
                sb.AppendLine($"年龄:{User.Age}");
                sb.AppendLine($"地址:{User.Address}");
                await new MessageDialog(sb.ToString()).ShowAsync();
            });
            return _registerCommand;
        }
    }

    public User User
    {
        get
        {
            return _user;
        }
    }
}

接下来编写 MainView 的代码:

<Page x:Class="UWPValidationDemo.Views.MainView"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:UWPValidationDemo.Views"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:vm="using:UWPValidationDemo.ViewModels"
      mc:Ignorable="d">
   
       
   
   
       <StackPanel HorizontalAlignment="Center"
                    Width="450">
           
               <TextBox Header="邮箱"
                         Text="{Binding Path=User.Email,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
               <TextBlock Text="{Binding Path=User.Errors[Email]}"
                           Foreground="Red">
           
           
               <PasswordBox Header="密码"
                             Password="{Binding Path=User.Password,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
               <TextBlock Text="{Binding Path=User.Errors[Password]}"
                           Foreground="Red">
           
           
               <TextBox Header="年龄"
                         Text="{Binding Path=User.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
               <TextBlock Text="{Binding Path=User.Errors[Age]}"
                           Foreground="Red">
           
           
               <TextBox Header="地址"
                         Text="{Binding Path=User.Address,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
               <TextBlock Text="{Binding Path=User.Errors[Address]}"
                           Foreground="Red">
           
           <Button Content="注册"
                    HorizontalAlignment="Center"
                    IsEnabled="{Binding Path=User.IsValid}"
                    Command="{Binding Path=RegisterCommand}">
           
               
               
           
       
   

TextBox、PasswordBox 用于填写,需要注意的是,Mode 需要为 TwoWay,UpdateSourceTrigger 为 PropertyChanged。TwoWay 是因为需要将 TextBox 的值写回 User 类的实例中,PropertyChanged 是因为我们需要实时更新验证,而不是控件失去焦点才验证。注册按钮则绑定 IsValid 到按钮的 IsEnabled 属性上。最后用一个 ItemsControl 来显示 User 类实例的所有错误,ItemsControl 有一个 ItemsSource 属性,绑定一个集合后,ItemsControl 将会显示每一个集合中元素,如果有用过 ListView 的话应该会很熟悉。

五、运行

不填写任何时:

填写错误时:

正确填写时:

六、结语

可见,通过将数据绑定和 Validation 结合起来后,我们再也不用写一堆又长又臭的条件判断代码了。(^o^)

另外,在上面的代码中,我们是在 Model 和 BindableBase 的继承关系中插入一个 VerifiableBase 类。同理,对于 ViewModel,我们也能够轻易写出一个 VerifiableViewModelBase 出来,用于 ViewModel 属性上的验证。

让IDataErrorInfo和ValidationAttribute结合实现基础验证

大家都知道微软在Asp.net Mvc中引入System.ComponentModel.DataAnnotations 命名空间,利用ValidationAttribute实现数据的校验,那么能不能把这种机制引入WinForm中呢?通过近两天的尝试发现,winform中很多组件都支持IDataErrorInfo接口实现的校验提示,我便想,如果让我们的这些校验特性的错误提示转到IDataErrorInfo接口实现中不久成功了吗?

下面就来尝试一下:

public abstract class EntityBase : INotifyPropertyChanged, IDataErrorInfo

{

    private readonly Dictionary_propertyGetters=new Dictionary();

    private readonly Dictionary_validators=new Dictionary();

    private readonly Type _type;

    protected EntityBase()

    {

        _type = GetType();

        LoadData();

    }

    #region 私?有D方?法¨

    private void LoadData()

    {

        PropertyInfo[] properties = _type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (PropertyInfo propertyInfo in properties)

        {

            //拥μ有D的?验é证¤特?性?

            object[] customAttributes = propertyInfo.GetCustomAttributes(typeof(ValidationAttribute), true);

            if (customAttributes.Length > 0)

            {

                _validators.Add(propertyInfo.Name, customAttributes as ValidationAttribute[]);

                _propertyGetters.Add(propertyInfo.Name, propertyInfo);

            }

        }

    }

    ///

    /// 属?性?更ü改?通¨知a

    ///

    ///

    private void OnPropertyChanged(string propertyName)

    {

        if (PropertyChanged != null)

        {

            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

        }

    }

    #endregion

    #region IDataErrorInfo Members

    ///

    /// 实μ现?IDataErrorInfo接ó口ú(获?取?校£验é未′通¨过y的?错í误ó提á示?)

    ///

    public string Error

    {

        get

        {

            IEnumerableerrors = from d in _validators

                                         from v in d.Value

                                         where !v.IsValid(_propertyGetters[d.Key].GetValue(this,null))

                                         select v.ErrorMessage;

            return string.Join(Environment.NewLine, errors.ToArray());

        }

    }

    ///

    /// 实μ现?IDataErrorInfo接ó口ú()

    ///

    ///

    ///

    public string this[string columnName]

    {

        get

        {

            if (_propertyGetters.ContainsKey(columnName))

            {

                object value = _propertyGetters[columnName].GetValue(this,null);

                string[] errors = _validators[columnName].Where(v => !v.IsValid(value))

                    .Select(v => v.ErrorMessage).ToArray();

                OnPropertyChanged("Error");

                return string.Join(Environment.NewLine, errors);

            }

            OnPropertyChanged("Error");

            return string.Empty;

        }

    }

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

}

通过代码中的注释信息,应该很容易理解的

下面让我们的类继承该抽象类就可以了,下面是显示效果截图,>,>,>,>

,>,>,>,>

短短的几十行代码,就实现了如此强大的功能,兴奋吧,当然要想让我们的框架实现更强大的验证功能,我们还可以实现自己的验证特性,这些在Asp.Net Mvc中已经司空见惯了,我就不多说了。

时间: 2024-07-30 04:09:20

Win10开发中使用 ValidationAttribute 实现数据验证的相关文章

android缓存处理-Android开发中客户端如何进行数据的存储

问题描述 Android开发中客户端如何进行数据的存储 Android开发中客户端如何进行数据的存储以达到数据的缓存来减少与服务端的交互次数,并设置缓存时间?(求大神指导,最好有完整的代码) 十分需要!求帮助! 解决方案 推荐在客户端使用sqlite来存放本地的数据.因为是数据库,所以很多底层的事情不用考虑了.要放缓存,直接建立一个字段,获取的时间,这样判断下,如果超过,就再次访问服务器获取.

项目开发中遇到的大量数据保存的问题

问题描述 项目开发中遇到的大量数据保存的问题 在项目开发中遇到的问题,是一个视频监控,有客户端和服务端.客户端播放视频的时候,需要 得到摄像头的ip地址,用户名,密码,通道这些信息,出于某些原因,现在这些数据是保存在服务端的 也就是说,客户端每次登陆需要向服务端请求这些数据.我的问题是,客户端对这些请求到的数据怎么保存? 要保存到全局变量里吗?因为摄像头比较多,所以数据也比较大,我不太清楚保存在全局变量里是不是合适. 解决方案 根据自己的情况,小的放在内存(局部或全剧都可以),大了放在文件中或数

ajax-web前端开发中使用接口获取数据,然后获取到的结果编译成表格形式

问题描述 web前端开发中使用接口获取数据,然后获取到的结果编译成表格形式 jquery的post请求从接口(json){ "m":"dh", "oper":"fdghgh", "sqlid":"2455", "params":{ "PHONE":"手机号码" } },中获取到结果后,然后将结果解析并编译成表格形式.这个怎么弄,

在Silverlight中进行基本的数据验证

Silverlight 2支持基本的数据验证功能.在Silverlight 2中,当我们把数据绑定到某个UI控件的时 候,该数据所具有的有效性规则也自动被绑定到了该UI控件上. 比如某个数据字段被设置为整数型,当我们用非整数型数据对该字段进行更新的时候就会发生错误. 我们就可以利用这个规则在UI中对输入数据进行验证.要做到这点,我们只要设置两个XAML属性,并在所 定义的事件中实现我们所期望的UI行为就可以了. 比如下面的XAML代码定义了一组控件,用户通过TextBox对数据进行更新: ﹤St

详解Angular开发中的登陆与身份验证_AngularJS

前言 由于 Angular 是单页应用,会在一开始,就把大部分的资源加载到浏览器中,所以就更需要注意验证的时机,并保证只有通过了验证的用户才能看到对应的界面. 本篇文章中的身份验证,指的是如何确定用户是否已经登陆,并确保在每次与服务器的通信中,都能够满足服务器的验证需求.注意,并不包括对具体是否具有某一个权限的判断. 对于登陆,主要是接受用户的用户名密码输入,提交到服务器进行验证,处理验证响应,在浏览器端构建身份验证数据. 实现身份验证的两种方式 目前,实现身份验证的方法,主要有两个大类: Co

win10开发中 UWP SplitView 控件讲解

本篇对适用于顶层导航的SplitView控件展开讨论. 首先SplitView是Win10 UWP新增的控件,以前虽然可以通过DockPanel模拟出类似的效果,但又哪里及得上M$原生支持的SplitView快捷方便呢. 至于为什么说SplitView适合顶层导航,可以参考目前尚为数不多的UWP APP,比如微博.QQ和网易等Win10 APP,基本都是通过SplitView将主界面分成左右两块Pane和Content,在屏幕宽度减至一定程度,自动隐藏Pane,仅显示Content.又或者将导航

PHP中常用的表单验证类

PHP动态网页开发中常用的表单验证类 <?php class class_post { //验证是否为指定长度的字母/数字组合 function fun_text1($num1,$num2,$str) { Return (preg_match("/^[a-zA-Z0-9]{".$num1.",".$num2."}$/",$str))?true:false; } //验证是否为指定长度数字 function fun_text2($num1,$

Swing通用数据验证模块

这段时间真是忙得要死,一方面要开发公司项目的系统框架,要将项目分成不同的子项目,编写核心 代码:另一方面要将极限编程(XP)引入团队开发,部署各类 XP需要的服务例如subversion啦,ant+ivy 啦,Hudson啦等等.顺便说句题外话,ubuntu还真是不是一般的好用,建议有能力的全部转到ubuntu上去 开发. 我目前开发的这个框架的客户端是具肥的客户端,也就是Swing客户端了.Swing应用相对于Web应用有 很多优势,因为它更肥.数据验证就是其中一个.当然现在的Web应用通过使

Excel数据验证怎么使用

  在往Excel中录入数据时,有时会遇到大量的重复数据,比如有这么一个工作表,第一列显示任务名,第二列显示任务执行者,第三列显示执行者的性别,其中任务执行者为固定的几个人交替执行,性别显然不是男就是女,也是重复性的.遇到类似这种情况,就可以通过Excel数据有效性的设定,既可避免重复输入,又能确保不会出错. 首先将光标移到"性别"一列的顶部,这时光标变成向下的箭头,此时点击将全选这一整列. 切换到"数据"选项卡,点击其中的"数据验证",从下拉菜