前言
在很多情况下,用户的输入不一定满足我们的设计要求,需要验证输入是否正确,传统的方案是拿到控件数据进行逻辑判定验证后,给用户弹窗提示。这种方法有点职责延后的感觉,数据视图层应该很好的处理用户的输入。使用数据验证器,就可以友好的提示错误信息,让后端不用过多的验证数据正确性。
一、使用效果
二、实现代码
1、校验逻辑
public class DataErrorInfoModel:NotifyBase
{
private string _userName;
[Required(ErrorMessage ="用户名不能为空")]
[StringLength(100,MinimumLength =2,ErrorMessage ="最小长度为2")]
public string UserName
{
get { return _userName; }
set
{
_userName = value;
this.Notify();
}
}
private string _email;
[Required(ErrorMessage = "邮箱不能为空")]
[StringLength(100, MinimumLength = 2, ErrorMessage = "最小长度为2")]
[RegularExpression("^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$",ErrorMessage = "请填写正确的邮箱地址!")]
public string Email
{
get { return _email; }
set
{
_email = value;
this.Notify();
}
}
}
public class NotifyBase : INotifyPropertyChanged, IDataErrorInfo
{
public string this[string columnName]
{
get
{
var errors = new List<ValidationResult>();
Validator.TryValidateProperty(this.GetType().GetProperty(columnName).GetValue(this, null),
new ValidationContext(this)
{MemberName=columnName
}, errors);
if (errors.Count>0)
{
return string.Join(Environment.NewLine, errors.Select(e => e.ErrorMessage).ToArray());
}
return "";
}
}
public string Error => null ;
public event PropertyChangedEventHandler PropertyChanged;
public void Notify([CallerMemberName] string propName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
2、WPF前台
<StackPanel Orientation="Horizontal" Grid.Row="2">
<TextBox Width="200" Height="40" VerticalContentAlignment="Center" Margin="10"
Template="{StaticResource ExceptionTextBoxTemplate}"
Validation.ErrorTemplate="{StaticResource ExceptionTextBoxTemplate}">
<TextBox.Text>
<Binding Path="DModel.UserName" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
</Binding>
</TextBox.Text>
</TextBox>
<TextBox Width="200" Height="40" VerticalContentAlignment="Center" Margin="10"
Template="{StaticResource ExceptionTextBoxTemplate}"
Validation.ErrorTemplate="{StaticResource ExceptionTextBoxTemplate}">
<TextBox.Text>
<Binding Path="DModel.Email" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
<ControlTemplate x:Key="ExceptionTextBoxTemplate">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" SnapsToDevicePixels="True" CornerRadius="5">
<Grid>
<ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" />
<Border Width="16" Height="16" CornerRadius="8" Margin="5" Background="Red" VerticalAlignment="Center" HorizontalAlignment="Right" Visibility="Collapsed" Name="validation" ToolTip="{Binding (Validation.Errors)[0].ErrorContent,RelativeSource={RelativeSource AncestorType=TextBox, Mode=FindAncestor}}">
<TextBlock Text="!" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" TargetName="border" Value="0.56" />
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.MouseOver.Border}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.Focus.Border}" />
</Trigger>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="BorderBrush" Value="Red" TargetName="border" />
<Setter Property="Visibility" Value="Visible" TargetName="validation" />
<Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent,RelativeSource={RelativeSource Self}}" />
<!--<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />-->
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
3、控件关联(绑定数据源)
this.DataContext = new DataErrorInfoViewModel();
三、FluentValidation扩展版本
1、校验逻辑
public class TemplateMatchParamValidator : AbstractValidator<TemplateMatchParam>
{
public TemplateMatchParamValidator()
{
/*
* .NotNull() 属性不是 null
* .NotEmpty() 属性不是 null、空字符串或空格 (或值类型的默认值, 例如 int 0)。
* .NotEqual() 值不等于特定值 (或不等于其他属性的值)
* .Equal() 值等于特定值 (或等于另一个属性的值)
* .Length() 长度位于指定范围内。但是, 它不能确保字符串属性是否为 null。
* .MaxLength() 长度不超过指定的值
* .MinLength() 长度不能小于指定的值。
* .LessThan() 值小于特定值 (或小于另一个属性的值)
* .LessThanOrEqualTo() 值小于等于特定值 (或小于等于另一个属性的值)
* .GreaterThan() 值大于特定值 (或大于另一个属性的值)
* .GreaterThanOrEqualTo() 值大于等于特定值 (或大于等于另一个属性的值)
* .Must() 值传递到一个委托中, 可以对该值执行自定义验证逻辑
* .Matches() 正则表达式验证
* .Email () 电子邮件验证
*
* .InclusiveBetween() 介于两者之间包括边界
* .ExclusiveBetween() 介于两者之间不包括边界
*
*/
RuleFor(vm => vm.Greed)
.NotNull()
.InclusiveBetween(0.01, 1)
.WithMessage($"请正确输入贪婪度:0.01-1");
RuleFor(vm => vm.MaxOverlay)
.NotNull()
.InclusiveBetween(0.01, 1)
.WithMessage($"请正确输入最大重叠:0.01-1");
RuleFor(vm => vm.MatchScore)
.NotNull()
.InclusiveBetween(0.1, 1)
.WithMessage($"请正确输入匹配分数:0.1-1");
RuleFor(vm => vm.Threshold)
.NotNull()
.InclusiveBetween(10, 200)
.WithMessage($"请正确输入梯度阈值:10-200");
RuleFor(vm => vm.MinLength)
.NotNull()
.InclusiveBetween(1, 100)
.WithMessage($"请正确输入最小长度:1-100");
RuleFor(vm => vm.TimeOut)
.NotNull()
.InclusiveBetween(1, 10000)
.WithMessage($"请正确输入超时:1-10000");
RuleFor(vm => vm.MinAngle)
.NotNull()
.InclusiveBetween(-180, 180)
.WithMessage($"请正确输入最小角度:-180-180");
RuleFor(vm => vm.MaxAngle)
.NotNull()
.InclusiveBetween(-180, 180)
.WithMessage($"请正确输入最大角度:-180-180");
RuleFor(vm => vm.MinRate)
.NotNull()
.InclusiveBetween(0.6, 1)
.WithMessage($"请正确输入最小比例:0.6-1");
RuleFor(vm => vm.MaxRate)
.NotNull()
.InclusiveBetween(1, 1.5)
.WithMessage($"请正确输入最大比例:1-1.5");
}
}
public class TemplateMatchParam : BindableBase, IDataErrorInfo
{
//其他代码省略
// <summary>
/// 当前属性设置错误信息,用于检验用户设置的参数是否正确
/// </summary>
public string Error
{
get
{
var results = validator.Validate(this);
if (results != null && results.Errors.Any())
{
var distinctRes = results.Errors.GroupBy(x => x.PropertyName).Select(y => y.FirstOrDefault());
var errors = string.Join(Environment.NewLine, distinctRes.Select(x => x.ErrorMessage).ToArray());
return errors;
}
if (LearnImage == null || !LearnImage.IsInitialized())
{
return "模板没有学习!";
}
return string.Empty;
}
}
public string this[string columnName]
{
get
{
if (validator == null)
{
validator = new TemplateMatchParamValidator();
}
var firstOrDefault = validator.Validate(this)
.Errors.FirstOrDefault(lol => lol.PropertyName == columnName);
return firstOrDefault?.ErrorMessage;
}
}
private TemplateMatchParamValidator validator { get; set; }
}
2、WPF前台
<TextBox
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource ParamSetHaveErrorTbStyle}"
Text="{Binding TemplateMatchParam.Threshold, ValidatesOnDataErrors=True}" />