实现方式:使用IDataErrorInfo接口实现验证。

 (1)类实现IDataErrorInfo接口;

public class Person : INotifyPropertyChanged, IDataErrorInfo
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}

private int _age;
public int Age
{
get { return _age; }
set
{
if (_age != value)
{
_age = value;
RaisePropertyChanged("Age");
}
}
}

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

public string this[string columnName]
{
get
{
if (columnName == "Age")
{
if (_age < 18)
{
return "年龄必须在18岁以上。";
}
}
return string.Empty;
}
}

public event PropertyChangedEventHandler PropertyChanged;

internal virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

(2)Xaml中绑定数据
    <TextBox Text="{Binding Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" Grid.Row="1" Grid.Column="1" Margin="5"/>
                  
(3)给TextBox定义一个Validation.ErrorTemplate模板;
 <Style TargetType="{x:Type TextBox}">
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <DockPanel LastChildFill="True">
                            <TextBlock DockPanel.Dock="Right" 
                                       Foreground="Red" 
                                       Margin="5,0,5,0"
                                       Text="*"/>
                            <Border BorderBrush="Red" BorderThickness="1">
                                <AdornedElementPlaceholder Name="ph"/>
                            </Border>
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
     
为了避免出现大量的 switch-case,并且将校验逻辑进行分离提高代码复用,于是 DataAnnotations 华丽登场。
改造下上面的 Person 类,加上 [Range]  ValidationAttribute:(需要添加 System.ComponentModel.DataAnnotations.dll)

[Range(19, 99, ErrorMessage="年龄必须在18岁以上。")]
public int Age
{
get { return _age; }
set
{
if (_age != value)
{
_age = value;
RaisePropertyChanged("Age");
}
}
}

修改 IDataErrorInfo 的索引器,让它通过 Validator 校验属性:

public string this[string columnName]
{
get
{
var vc = new ValidationContext(this, null, null);
vc.MemberName = columnName;
var res = new List<ValidationResult>();
var result = Validator.TryValidateProperty(this.GetType().GetProperty(columnName).GetValue(this, null), vc, res);
if (res.Count > 0)
{
return string.Join(Environment.NewLine, res.Select(r => r.ErrorMessage).ToArray());
}
return string.Empty;
}
}

用 DataAnnotions 后,Model 的更加简洁,校验也更加灵活。还可以利用 CustomerValidation 或者 自定义 ValidationAttribute 来进行校验逻辑的进一步分离,错误消息格式化。并且通过反射等技术,完全可以将 IDataErrorInfo 的实现抽成一个抽象类进行封装,编程更加便利。

(1) 自定义 ValidationAttribute
添加了一个针对上面 Person 的 Name 属性是否存在的校验:

class NameExists : ValidationAttribute
{
public override bool IsValid(object value)
{
var name = value as string;
// 这里可以到数据库等存储容器中检索
if (name != "Felix")
{
return false;
}
return true;
}

public override string FormatErrorMessage(string name)
{
return "请输入存在的用户名。";
}
}

将 NameExistsAttribute 添加到 Person.Name 属性上:

[NameExists]
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}

(2) 利用 CustomerValidationAttribute

先实现一个 public static 的校验方法(必须返回 ValidationResult )
 public class CustomerValidationUtils

}
return ValidationResult.Success;
}
}

然后在 Person 的 Name 属性上加上 CustomerValidation 特性:

[CustomValidation(typeof(CustomerValidationUtils), "CheckName")]
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}

在实际开发中,我们还经常使用 EF 等 ORM 来做数据访问层,Model 通常会由这个中间件自动生成(利用T4等代码生成工具)。而他们通常是 POCO 数据类型,这时候如何能把属性的校验特性加入其中呢。这时候, TypeDescriptor.AddProviderTransparent + AssociatedMetadataTypeTypeDescriptionProvider 可以派上用场,它可以实现在另一个类中增加对应校验特性来增强对原类型的元数据描述。按照这种思路,将上面的 Person 类分离成两个文件:第一个分离类,可以想象是中间件自动生成的 Model 类。第二个分离类中实现 IDataErrorInfo,并定义一个Metadata 类来增加校验特性。(EF CodeFirst 也可以使用这一思路)

Person.cs (原生的 Person 类,没有校验特性)

public partial class Person : INotifyPropertyChanged
{
private string _name;

public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}

private int _age;


public int Age
{
get { return _age; }
set
{
if (_age != value)
{
_age = value;
RaisePropertyChanged("Age");
}
}
}

public event PropertyChangedEventHandler PropertyChanged;

internal virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

PersonMetadata.cs (分离的 Person 类中,实现 IDataErrorInfo 接口,在其内部类中增加了校验特性

public partial class Person: IDataErrorInfo
{
class PersonMetadata
{
[Required]
[NameExists]
[CustomValidation(typeof(CustomerValidationUtils), "CheckName")]
public string Name { get; set; }

[Range(19, 99, ErrorMessage = "年龄必须在18岁以上。")]
public string Age { get; set; }
}

public string Error
{
get { throw new NotImplementedException(); }
}

public string this[string columnName]
{
get
{
return this.ValidateProperty<PersonMetadata>(columnName);
}
}
}

ValidateProperty 方法,是一个基于 object 类型的扩展方法。通过泛型<MetadataType>指定增强信息的类型。

public static class ValidationExtension
{
public static string ValidateProperty<metadatatype>(this object obj, string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
return string.Empty;

var targetType = obj.GetType();
//你也可以利用 MetadataType 在分离类上声明
//var targetMetadataAttr = targetType.GetCustomAttributes(false)
// .FirstOrDefault(a => a.GetType() == typeof(MetadataTypeAttribute)) as MetadataTypeAttribute;
//if (targetMetadataAttr != null && targetType != targetMetadataAttr.MetadataClassType)
//{
// TypeDescriptor.AddProviderTransparent(
// new AssociatedMetadataTypeTypeDescriptionProvider(targetType, targetMetadataAttr.MetadataClassType), targetType);
//}
if (targetType != typeof(MetadataType))
{
TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(targetType, typeof(MetadataType)), targetType);
}

var propertyValue = targetType.GetProperty(propertyName).GetValue(obj, null);
var validationContext = new ValidationContext(obj, null, null);
validationContext.MemberName = propertyName;
var validationResults = new List<validationresult>();

Validator.TryValidateProperty(propertyValue, validationContext, validationResults);

if (validationResults.Count > 0)
{
return validationResults.First().ErrorMessage;
}
return string.Empty;
}
}

相关文章:

  • 2022-12-23
  • 2021-10-27
  • 2022-01-18
  • 2022-01-04
  • 2021-07-16
  • 2022-12-23
  • 2021-08-11
猜你喜欢
  • 2021-11-03
  • 2021-10-23
  • 2022-01-09
  • 2021-07-01
  • 2022-12-23
相关资源
相似解决方案