【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 = "[email protected]",
        Password = "123",
        Age = 18,
        Address = "XYZ"
    };

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

    Debugger.Break();
}

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

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

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

如果修改 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<T>(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<string>
{
    private List<string> _messages = new List<string>();

    private List<ValidationResult> _results = new List<ValidationResult>();

    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 IEnumerator<string> GetEnumerator()
    {
        return _messages.GetEnumerator();
    }

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

这个对象是一个只读集合(实现 IReadOnlyList<T> 接口)。在构造函数中,验证 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">
    <Page.DataContext>
        <vm:MainViewModel></vm:MainViewModel>
    </Page.DataContext>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel HorizontalAlignment="Center"
                    Width="450">
            <StackPanel Margin="10">
                <TextBox Header="邮箱"
                         Text="{Binding Path=User.Email,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
                <TextBlock Text="{Binding Path=User.Errors[Email]}"
                           Foreground="Red"></TextBlock>
            </StackPanel>
            <StackPanel Margin="10">
                <PasswordBox Header="密码"
                             Password="{Binding Path=User.Password,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></PasswordBox>
                <TextBlock Text="{Binding Path=User.Errors[Password]}"
                           Foreground="Red"></TextBlock>
            </StackPanel>
            <StackPanel Margin="10">
                <TextBox Header="年龄"
                         Text="{Binding Path=User.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
                <TextBlock Text="{Binding Path=User.Errors[Age]}"
                           Foreground="Red"></TextBlock>
            </StackPanel>
            <StackPanel Margin="10">
                <TextBox Header="地址"
                         Text="{Binding Path=User.Address,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
                <TextBlock Text="{Binding Path=User.Errors[Address]}"
                           Foreground="Red"></TextBlock>
            </StackPanel>
            <Button Content="注册"
                    HorizontalAlignment="Center"
                    IsEnabled="{Binding Path=User.IsValid}"
                    Command="{Binding Path=RegisterCommand}"></Button>
            <StackPanel Margin="20">
                <TextBlock Text="所有错误:"></TextBlock>
                <ItemsControl ItemsSource="{Binding Path=User.Errors}"></ItemsControl>
            </StackPanel>
        </StackPanel>
    </Grid>
</Page>

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 属性上的验证。这里就大家自己编写了,最后放上这个 Demo 的代码:UWPValidationDemo.zip

时间: 2024-12-16 09:26:49

【Win10】使用 ValidationAttribute 实现数据验证的相关文章

我这么玩Web Api(二):数据验证,全局数据验证与单元测试

目录 一.模型状态 - ModelState 二.数据注解 - Data Annotations 三.自定义数据注解 四.全局数据验证 五.单元测试   一.模型状态 - ModelState 我理解的ModelState是微软在ASP.NET MVC中提出的一种新机制,它主要实现以下几个功能: 1. 保存客户端传过来的数据,如果验证不通过,把数据返回到客户端,这样可以保存用户输入,不需要重新输入. 2. 验证数据,以及保存数据对应的错误信息. 3. 微软的一种DRY(Don't Repeat

MVC 3 数据验证 Model Validation 详解

续我们前面所说的知识点进行下一个知识点的分析,这一次我们来说明一下数据验证.其实这是个很容易理解并掌握的地方,但是这会浪费大家狠多的时间,所以我来总结整理一下,节约一下大家宝贵的时间. 在MVC 3中 数据验证,已经应用的非常普遍,我们在web form时代需要在View端通过js来验证每个需要验证的控件值,并且这种验证的可用性很低.但是来到了MVC 新时代,我们可以通过MVC提供的数据验证Attribute来进行我们的数据验证.并且MVC 提供了客户端和服务器端 双层的验证,只有我们禁用了客户

使用Data Annotations进行手动数据验证

Data Annotations是在Asp.Net中用于表单验证的 它通过Attribute直接标记字段的有效性,简单且直观.在非Asp.Net程序中(如控制台程序),我们也可以使用Data Annotations进行手动数据验证的,一个简单的例子如下(需要添加System.ComponentModel.DataAnnotations.dll的引用): using System; using System.Collections.Generic; using System.Linq; using

(转)MVC 3 数据验证 Model Validation 详解

继续我们前面所说的知识点进行下一个知识点的分析,这一次我们来说明一下数据验证.其实这是个很容易理解并掌握的地方,但是这会浪费大家狠多的时间,所以我来总结整理一下,节约一下大家宝贵的时间. 在MVC 3中 数据验证,已经应用的非常普遍,我们在web form时代需要在View端通过js来验证每个需要验证的控件值,并且这种验证的可用性很低.但是来到了MVC 新时代,我们可以通过MVC提供的数据验证Attribute来进行我们的数据验证.并且MVC 提供了客户端和服务器端 双层的验证,只有我们禁用了客

asp.net mvc3 数据验证(三)—自定义数据注解

原文:asp.net mvc3 数据验证(三)-自定义数据注解         前两节讲的都是asp.net mvc3预先设定的数据注解,但是系统自由的数据注解肯定不适合所有的场合,所以有时候我们需要自定义数据注解. 自定义数据注解有两种,一种是直接写在模型对象中,这样做的好处是验证时只需要关心一种模型对象的验证逻辑,缺点也是显而易见的,那就是不能重用. 还有一种是封装在自定义的数据注解中,优点是可重用,缺点是需要应对不同类型的模型. 现在我们以封装在自定义数据注解中的方法为例看下如何在asp.

构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(33)-数据验证共享

原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(33)-数据验证共享 注:本节阅读需要有MVC 自定义验证的基础,否则比较吃力 一直以来表单的验证都是不可或缺的,微软的东西还是做得比较人性化的,从webform到MVC,都做到了双向验证 单单的用js实现的前端验证是极其不安全的,所以本次我们来看看MVC上的自带的注解验证,自定义验证 同样的MVC提供了一系列内置的数据验证注解 不为空验证  [Required(ErrorMessage = "不能为空

ASP.NET MVC ValidationAttribute 服务器端自定义验证

自定义服务端验证要继承自ValidationAttribute,并重写IsValid虚方法来自定义自己的验证规则,ValidationAttribute源码如下: 1 public abstract class ValidationAttribute : Attribute 2 { 3 //验证失败提示消息 4 public virtual string FormatErrorMessage(string name); 5 6 //自定义验证一 7 protected virtual Valid

MVC 数据验证【转】

[转自]http://www.cnblogs.com/dozer/archive/2010/04/12/MVC-DataAnnotations.html 作者Dozer 今天在这里给大家介绍一下MVC的数据验证框架. 在1.0版中,很多朋友提出了怎么使用客户端验证,今天找了一些资料,发现了客户端验证的方法. 1.MVC中的数据验证框架有何优点? 在Asp.net时代,或者没有使用MVC的验证框架,一般是在BLL层中进行数据验证,但是BLL层的返回值又只能返回一个东西,比如一个字符串,而实际情况中

&lt;转&gt;ASP.NET学习笔记之MVC 3 数据验证 Model Validation 详解

MVC 3 数据验证 Model Validation 详解 在MVC 3中 数据验证,已经应用的非常普遍,我们在web form时代需要在View端通过js来验证每个需要验证的控件值,并且这种验证的可用性很低.但是来到了MVC 新时代,我们可以通过MVC提供的数据验证Attribute来进行我们的数据验证.并且MVC 提供了客户端和服务器端 双层的验证,只有我们禁用了客户端js以后,也会执行服务端验证,所以大大提高了我们的开发进度.今天我们就一起以一个初学者的身份来进入数据验证的殿堂. 首先,