WPF中使用Data Annotations验证Model

.NET Framework中System.ComponentModel.DataAnnotations提供了很多属性来验证对象的属性。可以在C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\{.NET Version}\路径下面找到System.ComponentModel.DataAnnotations.dll

public class User
{
    [Required]
    [StringLength(20)]
    public string Name { get; set; }

    [Range(1,120)]
    public int Age { get; set; }
}

检查一个实例是否合法有效,使用下面的代码,具体可以参考: https://msdn.microsoft.com/en-us/library/dd411772%28v=vs.110%29.aspx

Validator.TryValidateObject(obj,new ValidationContext(obj),results,true);
static void Main(string[] args)
{
    ICollection<ValidationResult> results = null;

    User invalidUser = new User
    {
        Name = "My name is System.ComponentModel.DataAnnotations",
        Age = -1,
    };

    if(!Validate(invalidUser, out results))
    {
        Console.WriteLine(string.Join("\n", results.Select(o=>o.ErrorMessage)));
    }
    else
    {
        Console.WriteLine("I am a valid object.");
    }

    Console.ReadKey();
}

static bool Validate<T>(T obj, out ICollection<ValidationResult> results)
{
    results = new List<ValidationResult>();

    return Validator.TryValidateObject(obj, new ValidationContext(obj), results, true);
}

代码中实例化了一个非法的User,代码执行结果如下:

这些ErrorMessage是.NET提供的,如果需要自定义错误信息可以在Attribute上增加ErrorMessage,代码如下:

public class User
{
    [Required]
    [StringLength(20, ErrorMessage ="Out of range~")]
    public string Name { get; set; }

    [Range(1,120, ErrorMessage ="Not a valid age.")]
    public int Age { get; set; }
}

执行结果如下:

如果将User的属性修改为合法的值,结果如下:

User validUser = new User
{
    Name = "Hellen",
    Age = 18,
};

在WPF中,继承IDataErrorInfo接口,通过IDataErrorInfo来传递Data Annotation的ErrorMessage。

class PropertyValidateModel : IDataErrorInfo
{
    public string this[string columnName]
    {
        get
        {
            List<ValidationResult> validationResults = new List<ValidationResult>();

            bool result = Validator.TryValidateProperty(
                GetType().GetProperty(columnName).GetValue(this),
                new ValidationContext(this)
                {
                    MemberName = columnName
                },
                validationResults);

            if (result)
                return null;

            return validationResults.First().ErrorMessage;
        }
    }

    public string Error
    {
        get
        {
            return null;
        }
    }
}

这里只验证单个属性,下面是Model类,注意:Model需要继承INotifyPropertyChanged接口,直接看代码,

class User : PropertyValidateModel, INotifyPropertyChanged
{
    private string _name = string.Empty;

    private int _age = 0;

    [Required]
    [StringLength(20)]
    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            if(_name != value)
            {
                _name = value;

                RaisePropertyChanged("Name");
            }
        }
    }

    [Required]
    [Range(1,120)]
    public int Age
    {
        get
        {
            return _age;
        }
        set
        {
            if(_age != value)
            {
                _age = value;

                RaisePropertyChanged("Age");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

下面看一下UI这一块的ErrorMessage绑定

<StackPanel>
    <TextBlock Text="Name: "/>
    <TextBox Text="{Binding User.Name,UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" Margin="0,10">
        <Validation.ErrorTemplate>
            <ControlTemplate>
                <StackPanel>
                    <AdornedElementPlaceholder x:Name="textBox" />
                    <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
                </StackPanel>
            </ControlTemplate>
        </Validation.ErrorTemplate>
    </TextBox>
    <TextBlock Text="Age" />
    <TextBox Text="{Binding User.Age, UpdateSourceTrigger=LostFocus, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" Margin="0,10">
        <Validation.ErrorTemplate>
            <ControlTemplate>
                <StackPanel>
                    <AdornedElementPlaceholder x:Name="textBox" />
                    <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
                </StackPanel>
            </ControlTemplate>
        </Validation.ErrorTemplate>
    </TextBox>
</StackPanel>

运行结果如下:

补充一个内容:

如何自定义一个ValidationAttribute。只需要继承ValidationAttribute,并重写IsValid方法即可。例如:

    public class DivisibleBy7Attribute : ValidationAttribute
    {
        public DivisibleBy7Attribute()
            :base("{0} value is not divisible by 7")
        {
        }

        protected override ValidationResult IsValid(
            object value, 
            ValidationContext validationContext)
        {
            decimal val = (decimal)value;

            bool vaild = val % 7 == 0;

            if (vaild)
                return null;
            return new ValidationResult(base.FormatErrorMessage(validationContext.MemberName));
        }
    }

感谢您的阅读!代码点击这里下载。

posted @ 2017-08-16 11:34  Yang-Fei  阅读(1356)  评论(1编辑  收藏  举报