首页 > 编程语言 >FluentValidation在C#的应用

FluentValidation在C#的应用

时间:2024-01-26 21:35:15浏览次数:38  
标签:string 验证 C# FluentValidation private 应用 validateResult public 属性

FluentValidation在C# WPF中的应用 

 

1. 引言

在.NET开发领域,FluentValidation以其优雅、易扩展的特性成为开发者进行属性验证的首选工具。它不仅适用于Web开发,如MVC、Web API和ASP.NET CORE,同样也能完美集成在WPF应用程序中,提供强大的数据验证功能。本文将深入探讨如何在C# WPF项目中运用FluentValidation进行属性验证,并展示如何通过MVVM模式实现这一功能。

2. 功能概览

我们的目标是构建一个WPF应用程序,它能够通过FluentValidation实现以下验证功能:

  1. 验证ViewModel层的基本数据类型属性,如int、string等。
  2. 对ViewModel中的复杂属性进行验证,这包括对象属性的子属性以及集合属性。
  3. 提供两种直观的错误提示样式,以增强用户体验。

先看实现效果图:

3. 解决问题与探索

在调研过程中,我发现FluentValidation官方文档主要关注于Web应用的验证。对于WPF和复杂属性的验证,官方文档提供的示例有限。然而,通过深入研究和实践,我找到了将FluentValidation与WPF结合使用的有效方法,特别是针对复杂属性的验证。

4. 开发步骤

4.1. 创建工程、库引入

首先,创建一个新的WPF项目,并引入FluentValidation库用于属性验证,以及Prism.Wpf库以简化MVVM模式的实现。

  <ItemGroup>
  <PackageReference Include="FluentValidation" Version="11.9.0" />
  <PackageReference Include="Prism.Wpf" Version="9.0.271-pre" />
  </ItemGroup>

4.2. 创建实体类

我创建了两个实体类:Student和Field,分别代表对象属性和集合项属性。这两个类都实现了IDataErrorInfo接口:

  1. IDataErrorInfo接口常用于提供实体数据验证的错误信息。这个接口包含两个成员:一个索引器(this[string columnName])和一个Error属性。索引器用于按属性名称提供错误信息,而Error属性则用于提供整个实体的错误概述。
  2. 两个实体类和另外在后面提及的ViewModel中也实现IDataErrorInfo接口,并在this[string columnName]索引器和Error属性中使用FluentValidation来验证属性。

4.2.1. 普通类 - Student

学生类包含5个属性:名字、年龄、邮政编码、最小值和最大值,其中最小值和最大值涉及关联验证,即最小值变化后通知最大值验证,反之同理。

  /// <summary>
  /// 学生实体
  /// 继承BindableBase,即继承属性变化接口INotifyPropertyChanged
  /// 实现IDataErrorInfo接口,用于FluentValidation验证,必须实现此接口
  /// </summary>
  public class Student : BindableBase, IDataErrorInfo
  {
  private int _age;
  private string? _name;
  private string? _zip;
  private readonly StudentValidator _validator = new();
   
  public string? Name
  {
  get => _name;
  set => SetProperty(ref _name, value);
  }
   
  public int Age
  {
  get => _age;
  set => SetProperty(ref _age, value);
  }
   
  public string? Zip
  {
  get => _zip;
  set => SetProperty(ref _zip, value);
  }
   
  private int _minValue;
   
  public int MinValue
  {
  get => _minValue;
  set
  {
  SetProperty(ref _minValue, value);
   
  // 关联更新最大值验证
  RaisePropertyChanged(nameof(MaxValue));
  }
  }
   
  private int _maxValue;
   
  public int MaxValue
  {
  get => _maxValue;
  set
  {
  SetProperty(ref _maxValue, value);
   
  // 关联更新最小值验证
  RaisePropertyChanged(nameof(MinValue));
  }
  }
   
  public string this[string columnName]
  {
  get
  {
  var validateResult = _validator.Validate(this);
  if (validateResult.IsValid)
  {
  return string.Empty;
  }
   
  var firstOrDefault =
  validateResult.Errors.FirstOrDefault(error => error.PropertyName == columnName);
  return firstOrDefault == null ? string.Empty : firstOrDefault.ErrorMessage;
  }
  }
   
  public string Error
  {
  get
  {
  var validateResult = _validator.Validate(this);
  if (validateResult.IsValid)
  {
  return string.Empty;
  }
   
  var errors = string.Join(Environment.NewLine, validateResult.Errors.Select(x => x.ErrorMessage).ToArray());
  return errors;
  }
  }
  }

上面关键代码在public string this[string columnName]:这里进行输入表单项的数据校验,FluentValidation调用就在这里,校验逻辑封装在StudentValidator,表单输入时会实时调用该处代码,columnName表示表单项的列名,就是View绑定的属性名。

4.2.2. 集合类 - Field

此类用作ViewModel中的集合项使用,模拟动态表单数据校验,简单包含4个属性:字段名称、字段显示名称、数据类型、数据值,表单主要根据数据类型验证输入的数据值是否合法。同样此实体需要继承IDataErrorInfo接口,用于触发FluentValidation验证使用。

  /// <summary>
  /// 扩展字段,用于生成动态表单
  /// 继承BindableBase,即继承属性变化接口INotifyPropertyChanged
  /// 实现IDataErrorInfo接口,用于FluentValidation验证,必须实现此接口
  /// </summary>
  public class Field : BindableBase, IDataErrorInfo
  {
  private string? _value;
  private readonly FieldValidator _validator = new();
   
   
  public Field(DataType type, string typeLabel, string name, string value)
  {
  Type = type;
  TypeLabel = typeLabel;
  Name = name;
  Value = value;
  }
   
  /// <summary>
  /// 数据类型
  /// </summary>
  public DataType Type { get; set; }
   
  /// <summary>
  /// 数据类型名称
  /// </summary>
  public string TypeLabel { get; set; }
   
  /// <summary>
  /// 名称
  /// </summary>
  public string Name { get; set; }
   
  /// <summary>
  /// 值
  /// </summary>
  public string? Value
  {
  get => _value;
  set => SetProperty(ref _value, value);
  }
   
  public string this[string columnName]
  {
  get
  {
  var validateResult = _validator.Validate(this);
  if (validateResult.IsValid)
  {
  return string.Empty;
  }
   
  var firstOrDefault =
  validateResult.Errors.FirstOrDefault(error => error.PropertyName == columnName);
  return firstOrDefault == null ? string.Empty : firstOrDefault.ErrorMessage;
  }
  }
   
  public string Error
  {
  get
  {
  var validateResult = _validator.Validate(this);
  if (validateResult.IsValid)
  {
  return string.Empty;
  }
   
  var errors = string.Join(Environment.NewLine, validateResult.Errors.Select(x => x.ErrorMessage).ToArray());
  return errors;
  }
  }
  }
   
  public enum DataType
  {
  Text,
  Number,
  Date
  }

看上面代码,public string this[string columnName]代码处写法和Student类一样,只是_validator变量类型不同,前者为StudentValidator,这里是FieldValidator,下面我们看看这两个类怎么写。

4.3. 创建验证器

对于每个实体类,我都创建了一个对应的验证器类:StudentValidatorFieldValidator。这些验证器类继承自AbstractValidator,并在其中定义了验证规则。验证属性的写法有两种:

  1. 可以在实体属性上方添加特性(本文不作特别说明,百度文章介绍很多);

  2. 通过代码的形式添加,如下方,创建一个验证器类,继承自AbstractValidator,在此验证器构造函数中写规则验证属性,方便管理。

本文使用第二种,下面通过创建StudentValidatorFieldValidator两个验证器类介绍。

4.3.1. StudentValidator

这是学生验证器StudentValidator,需要继承AbstractValidator,泛型指定前面需要验证的实体类Student

  public class StudentValidator : AbstractValidator<Student>
  {
  public StudentValidator()
  {
  RuleFor(vm => vm.Name)
  .NotEmpty()
  .WithMessage("请输入学生姓名!")
  .Length(5, 30)
  .WithMessage("学生姓名长度限制在5到30个字符之间!");
   
  RuleFor(vm => vm.Age)
  .GreaterThanOrEqualTo(0)
  .WithMessage("学生年龄为整数!")
  .ExclusiveBetween(10, 150)
  .WithMessage("请正确输入学生年龄(10-150)");
   
  _ = RuleFor(vm => vm.Zip)
  .NotEmpty()
  .WithMessage("邮政编码不能为空!")
  .Must(BeAValidZip)
  .WithMessage("邮政编码由六位数字组成。");
   
  RuleFor(model => model.MinValue).Must((model, minValue) => minValue < model.MaxValue).WithMessage("最小值应该小于最大值");
   
  RuleFor(model => model.MaxValue).Must((model, maxValue) => maxValue > model.MinValue).WithMessage("最大值应该大于最小值");
  }
   
  private static bool BeAValidZip(string? zip)
  {
  if (string.IsNullOrEmpty(zip))
  {
  return false;
  }
   
  var regex = new Regex(@"\d{6}");
  return regex.IsMatch(zip);
  }
  }

代码简单,使用到数字的大小和范围验证(见Age)、字符串不能为空和长度限制(见Name)、字符串正则表达式验证(见Zip)、多属性关联验证(最小值和最大值,这里配合属性set时通知其他属性验证通知RaisePropertyChanged(nameof(MaxValue));)。

4.3.2. FieldValidator

动态表单数据值校验器,同理需要继承AbstractValidator,泛型指定前面需要验证的实体类Field::

  public class FieldValidator : AbstractValidator<Field>
  {
  public FieldValidator()
  {
  RuleFor(field => field.Value)
  .Must((field, value) => (field.Type == DataType.Text && !string.IsNullOrWhiteSpace(value))
  || (field.Type == DataType.Number && double.TryParse(value, out _))
  || (field.Type == DataType.Date && DateTime.TryParse(value, out _)))
  .WithMessage("1.文本不能为空;2.数字类型请填写数字;3.日志类型请填写日期类型");
  }
  }

这里写的简单了点:

  1. 文本数据类型,值不能为空;
  2. 数字数据类型,必须是double类型;
  3. 日期类型,必须能使用DateTime转换;

本文只做简单演示,多种数据类型放Must方法中做统一验证,验证出错给出统一的提示信息,读者可按实际情况修改。

4.3.3. StudentViewModelValidator

此外,我还创建了一个StudentViewModelValidator,用于验证ViewModel层的属性。这个验证器能够处理基本数据类型、对象属性以及集合属性的验证。

  public class StudentViewModelValidator : AbstractValidator<StudentViewModel>
  {
  public StudentViewModelValidator()
  {
  RuleFor(vm => vm.Title)
  .NotEmpty()
  .WithMessage("标题长度不能为空!")
  .Length(5, 30)
  .WithMessage("标题长度限制在5到30个字符之间!");
   
  RuleFor(vm => vm.CurrentStudent).SetValidator(new StudentValidator());
   
  RuleForEach(vm => vm.Fields).SetValidator(new FieldValidator());
  }
  }
  1. Title用于关联验证基本数据类型(string类型);
  2. CurrentStudent用于验证对象属性(Student类的实例),设置验证该属性时使用StudentValidator验证器;
  3. Fields用于验证集合属性(ObservableCollection<Field>),设置验证该属性子项时使用FieldValidator验证器,注意前面使用的RuleForEach表示关联集合中的项验证器。

4.4. ViewModel层实现

StudentViewModelStudent实体类结构类似,都需要实现IDataErrorInfo接口,该类由一个简单的string属性(Title)和一个复杂的Student对象属性(CurrentStudent)、集合属性ObservableCollection<Field> Fields组成,代码如下:

  /// <summary>
  /// 视图ViewModel
  /// 继承BindableBase,即继承属性变化接口INotifyPropertyChanged
  /// 实现IDataErrorInfo接口,用于FluentValidation验证,必须实现此接口
  /// </summary>
  public class StudentViewModel : BindableBase, IDataErrorInfo
  {
  private Student _currentStudent;
  private string _title;
   
  private readonly StudentViewModelValidator _validator;
   
  public string Title
  {
  get => _title;
  set => SetProperty(ref _title, value);
  }
   
  public Student CurrentStudent
  {
  get => _currentStudent;
  set => SetProperty(ref _currentStudent, value);
  }
   
  public ObservableCollection<Field> Fields { get; } = new();
   
  private DelegateCommand _saveCommand;
   
  public DelegateCommand SaveCommand => _saveCommand ??= new DelegateCommand(HandleSaveCommand,
  HandleCanExecuteSaveCommand);
   
  private DelegateCommand _cancelCommand;
   
  public DelegateCommand CancelCommand =>
  _cancelCommand ??= new DelegateCommand(HandleCancelCommand, () => true);
   
  public StudentViewModel()
  {
  _validator = new StudentViewModelValidator();
  CurrentStudent = new Student
  {
  Name = "李刚的儿",
  Age = 23
  };
  Fields.Add(new Field(DataType.Text, "文本,比如:四川省成都市场", "地址", ""));
  Fields.Add(new Field(DataType.Number, "数字,比如:12", "工龄", ""));
  Fields.Add(new Field(DataType.Date, "时间,比如:2023-09-26 05:13:23", "培训时间", ""));
   
  PropertyChanged += Validate;
  CurrentStudent.PropertyChanged += Validate;
  foreach (var field in Fields)
  {
  field.PropertyChanged += Validate;
  }
  }
   
  ~StudentViewModel()
  {
  PropertyChanged -= Validate;
  CurrentStudent.PropertyChanged -= Validate;
  foreach (var field in Fields)
  {
  field.PropertyChanged -= Validate;
  }
  }
   
  private void Validate(object sender, PropertyChangedEventArgs e)
  {
  _isCanExecuteSaveCommand = _validator.Validate(this).IsValid;
  SaveCommand.RaiseCanExecuteChanged();
  }
   
  private void HandleSaveCommand()
  {
  var validateResult = _validator.Validate(this);
  if (validateResult.IsValid)
  {
  MessageBox.Show("看到我说明验证成功!");
  }
  else
  {
  var errorMsg = string.Join(Environment.NewLine,
  validateResult.Errors.Select(x => x.ErrorMessage).ToArray());
  MessageBox.Show($"慌啥子嘛,你再检查下输入噻:\r\n{errorMsg}");
  }
  }
   
  private bool _isCanExecuteSaveCommand;
   
  private bool HandleCanExecuteSaveCommand()
  {
  return _isCanExecuteSaveCommand;
  }
   
  private void HandleCancelCommand()
  {
  MessageBox.Show("我啥都不做,退休了");
  }
   
  public string this[string columnName]
  {
  get
  {
  var validateResult = _validator.Validate(this);
  if (validateResult.IsValid)
  {
  return string.Empty;
  }
   
  var firstOrDefault =
  validateResult.Errors.FirstOrDefault(error => error.PropertyName == columnName);
  return firstOrDefault == null ? string.Empty : firstOrDefault.ErrorMessage;
  }
  }
   
  public string Error
  {
  get
  {
  var validateResult = _validator.Validate(this);
  if (validateResult.IsValid)
  {
  return string.Empty;
  }
   
  var errors = string.Join(Environment.NewLine, validateResult.Errors.Select(x => x.ErrorMessage).ToArray());
  return errors;
  }
  }
  }

ViewModel属性验证和StudentField类似,这里我加上了保存(SaveCommand)和取消(CancelCommand)两个命令,其中保存命令需要所有属性验证通过才可用,通过注册属性的变化事件PropertyChanged,在变化事件处理程序中验证:

  PropertyChanged += Validate;
  CurrentStudent.PropertyChanged += Validate;
  foreach (var field in Fields)
  {
  field.PropertyChanged += Validate;
  }
  private void Validate(object sender, PropertyChangedEventArgs e)
  {
  _isCanExecuteSaveCommand = _validator.Validate(this).IsValid;
  SaveCommand.RaiseCanExecuteChanged();
  }

4.5. 视图层实现

在视图层,我创建了一个用户控件StudentView,用于显示输入表单和验证结果。通过绑定ViewModel层的属性和命令,视图层能够与ViewModel层进行交互,并实时显示验证错误。这里比较简单,提供简单属性标题(Title)、复杂属性(包括学生姓名(CurrentStudent.Name)、学生年龄( CurrentStudent .Age)、学生邮政编码( CurrentStudent .Zip)、最小值(CurrentStudent.MinValue)、最大值(CurrentStudent.MaxValue))验证、集合属性验证(Fields),xaml代码如下:

  <UserControl
  x:Class="WpfFluentValidation.Views.StudentView"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:models="clr-namespace:WpfFluentValidation.Models"
  xmlns:vm="clr-namespace:WpfFluentValidation.ViewModels"
  mc:Ignorable="d" Padding="10">
  <UserControl.DataContext>
  <vm:StudentViewModel />
  </UserControl.DataContext>
  <Grid>
  <Grid.RowDefinitions>
  <RowDefinition Height="*" />
  <RowDefinition Height="50" />
  </Grid.RowDefinitions>
  <ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
  <Grid>
  <Grid.RowDefinitions>
  <RowDefinition Height="Auto" />
  <RowDefinition Height="Auto" />
  <RowDefinition Height="*" />
  </Grid.RowDefinitions>
   
  <GroupBox Header="ViewModel直接属性验证">
  <StackPanel>
  <Label Content="标题:" />
  <TextBox Style="{StaticResource Styles.TextBox.ErrorStyle1}"
  Text="{Binding Title, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
  </StackPanel>
  </GroupBox>
   
  <GroupBox Grid.Row="1" Header="ViewModel对象属性CurrentStudent的属性验证">
  <StackPanel>
  <StackPanel>
  <Label Content="姓名:" />
  <TextBox Style="{StaticResource Styles.TextBox.ErrorStyle2}"
  Text="{Binding CurrentStudent.Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
  </StackPanel>
  <StackPanel>
  <Label Content="年龄:" />
  <TextBox Style="{StaticResource Styles.TextBox.ErrorStyle2}"
  Text="{Binding CurrentStudent.Age, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
  </StackPanel>
  <StackPanel>
  <Label Content="邮编:" />
  <TextBox Style="{StaticResource Styles.TextBox.ErrorStyle2}"
  Text="{Binding CurrentStudent.Zip, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
  </StackPanel>
  <StackPanel>
  <Label Content="最小值:" />
  <TextBox Style="{StaticResource Styles.TextBox.ErrorStyle2}"
  Text="{Binding CurrentStudent.MinValue, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
  </StackPanel>
  <StackPanel>
  <Label Content="最大值:" />
  <TextBox Style="{StaticResource Styles.TextBox.ErrorStyle2}"
  Text="{Binding CurrentStudent.MaxValue, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
  </StackPanel>
  </StackPanel>
  </GroupBox>
   
  <GroupBox Grid.Row="2" Header="ViewModel集合属性Fields的属性验证">
  <ItemsControl ItemsSource="{Binding Fields}">
  <ItemsControl.ItemTemplate>
  <DataTemplate DataType="{x:Type models:Field}">
  <Border Padding="10">
  <Grid>
  <Grid.RowDefinitions>
  <RowDefinition Height="Auto" />
  <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>
  <TextBlock Margin="0,0,0,5">
  <Run Text="{Binding Name}" />
  <Run Text="(" />
  <Run Text="{Binding TypeLabel}" />
  <Run Text=")" />
  </TextBlock>
  <TextBox Grid.Row="1" Style="{StaticResource Styles.TextBox.ErrorStyle2}"
  Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
  </Grid>
  </Border>
  </DataTemplate>
  </ItemsControl.ItemTemplate>
  </ItemsControl>
  </GroupBox>
  </Grid>
  </ScrollViewer>
   
  <StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal">
  <Button Content="取消" Command="{Binding CancelCommand}" Style="{StaticResource Styles.Button.Common}"
  Margin="0 3 40 3" />
  <Button Content="提交" Command="{Binding SaveCommand}" Style="{StaticResource Styles.Button.Blue}"
  Margin="0 3 10 3" />
  </StackPanel>
  </Grid>
  </UserControl>

4.6. 错误提示样式

为了提升用户体验,我定义了两种错误提示样式:一种是通过红色图标提示输入框旁边的错误,另一种是在输入框右侧显示错误文字。这些样式定义在App.xaml中,并可以在整个应用程序中复用。

  <Application
  x:Class="WpfFluentValidation.App"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  StartupUri="MainWindow.xaml">
  <Application.Resources>
  <Style TargetType="StackPanel">
  <Setter Property="Margin" Value="0,5" />
  </Style>
  <!-- 第一种错误样式,红色边框 -->
  <Style x:Key="Styles.TextBox.ErrorStyle1" TargetType="{x:Type TextBox}">
  <Setter Property="Width" Value="250" />
  <Setter Property="Height" Value="25" />
  <Setter Property="HorizontalAlignment" Value="Left" />
  <Setter Property="Validation.ErrorTemplate">
  <Setter.Value>
  <ControlTemplate>
  <DockPanel>
  <Grid
  Width="16"
  Height="16"
  Margin="3,0,0,0"
  VerticalAlignment="Center"
  DockPanel.Dock="Right">
  <Ellipse
  Width="16"
  Height="16"
  Fill="Red" />
  <Ellipse
  Width="3"
  Height="8"
  Margin="0,2,0,0"
  HorizontalAlignment="Center"
  VerticalAlignment="Top"
  Fill="White" />
  <Ellipse
  Width="2"
  Height="2"
  Margin="0,0,0,2"
  HorizontalAlignment="Center"
  VerticalAlignment="Bottom"
  Fill="White" />
  </Grid>
  <Border
  BorderBrush="Red"
  BorderThickness="2"
  CornerRadius="2">
  <AdornedElementPlaceholder />
  </Border>
  </DockPanel>
  </ControlTemplate>
  </Setter.Value>
  </Setter>
  <Style.Triggers>
  <Trigger Property="Validation.HasError" Value="true">
  <Setter Property="ToolTip"
  Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
  </Trigger>
  </Style.Triggers>
  </Style>
   
  <!-- 第二种错误样式,右键文字提示 -->
  <Style x:Key="Styles.TextBox.ErrorStyle2" TargetType="{x:Type TextBox}">
  <Setter Property="Width" Value="250" />
  <Setter Property="Height" Value="25" />
  <Setter Property="VerticalContentAlignment" Value="Center" />
  <Setter Property="Padding" Value="5,0" />
  <Setter Property="HorizontalAlignment" Value="Left" />
  <Setter Property="Validation.ErrorTemplate">
  <Setter.Value>
  <ControlTemplate>
  <StackPanel Orientation="Horizontal">
  <AdornedElementPlaceholder x:Name="textBox" />
  <Grid>
  <TextBlock Margin="10 0 0 0" Width="130"
  Foreground="Red" TextWrapping="Wrap"
  Text="{Binding [0].ErrorContent}" />
  </Grid>
  </StackPanel>
  </ControlTemplate>
  </Setter.Value>
  </Setter>
  <Style.Triggers>
  <Trigger Property="Validation.HasError" Value="true">
  <Setter Property="ToolTip"
  Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
  <Setter Property="Background" Value="LightPink" />
  <Setter Property="BorderBrush" Value="Red" />
  <Setter Property="Foreground" Value="White" />
  </Trigger>
  </Style.Triggers>
  </Style>
   
  <Style TargetType="GroupBox">
  <Setter Property="Margin" Value="5" />
  <Setter Property="Padding" Value="2" />
  <Setter Property="BorderBrush" Value="#FF0078D7" />
  <Setter Property="BorderThickness" Value="2" />
  <Setter Property="Background" Value="#FFF0F0F0" />
  <Setter Property="Foreground" Value="#FF0078D7" />
  <Setter Property="FontWeight" Value="Bold" />
  </Style>
   
  <Style x:Key="Styles.Button.Common" TargetType="{x:Type Button}">
  <Setter Property="MinWidth" Value="75" />
  <Setter Property="MinHeight" Value="25" />
  <Setter Property="Background" Value="White" />
  <Setter Property="Foreground" Value="Black" />
  </Style>
   
  <Style
  x:Key="Styles.Button.Blue"
  BasedOn="{StaticResource ResourceKey=Styles.Button.Common}"
  TargetType="{x:Type Button}">
  <Setter Property="Background" Value="Green" />
  <Setter Property="Foreground" Value="White" />
  </Style>
  </Application.Resources>
  </Application>

5. 效果展示

通过上述步骤的实现,我们得到了一个功能完善的WPF应用程序。它能够根据用户输入实时进行验证,并提供直观的错误提示。当所有属性都验证通过时,提交按钮将变为可用状态。

6. 源码分享

为了方便读者学习和交流,本文将所有代码同步到了Gitee和Github平台上。欢迎感兴趣的开发者访问以下链接获取源码:

7. 总结

通过本文的介绍和实践,我们成功将FluentValidation应用于C# WPF项目中,实现了对ViewModel层属性的全面验证。这不仅提升了数据的安全性和准确性,也为用户提供了更好的交互体验。希望本文能对广大开发者在WPF项目中使用FluentValidation提供有益的参考和启示。

参考:

时间如流水,只能流去不流回。

标签:string,验证,C#,FluentValidation,private,应用,validateResult,public,属性
From: https://www.cnblogs.com/Leo_wl/p/17990776

相关文章

  • 无涯教程-Scala - do-while 循环函数
    与while循环在循环顶部测试循环条件不同,do-while循环在循环底部检查其条件,do-while循环与while循环相似,除了保证do-while循环至少执行一次do-while-语法以下是do-while循环的语法。do{statement(s);}while(condition);do-while-流程图尝试使用以下示例......
  • edusrc—freemarker模板注入RCE
    freemarker简述FreeMarker是一款模板引擎:即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件信息收集开局一个登入框弱口令admin/123456登入......
  • 「ABC107C」 Candles
    题意在一个数轴上有\(N(1\leN\le10^5)\)个点,给定每个点的位置\(x_i\)(横坐标),保证\(x_i\)单调递增且\(|x_i|\le10^8\)。求从原点出发,到达不同的\(K(1\leK\leN)\)个点所经过的最小距离。分析我们看到\(N\)的范围正好卡掉\(O(N^2)\),但\(O(N)\)可以过不然怎......
  • 极速搭建基于mvc5的最小框架
    前言开发环境vs2019创建项目项目文件结构新建控制器新建视图编写视图代码编写控制器代码修改默认路由运行测试查看结果......
  • 「NEERC2014」 Knockout Racing
    题意给\(N(1\leN\le1000)\)辆在一条直线上跑的车,每辆车在区间\([A_i,B_i](0\leA_i,B_i\le10^9,A_i\neB_i)\)中行驶,可以把速度都看作一个单位速度。然后给\(M(1\leM\le1000)\)个询问,每个询问都有一组\(X_i,Y_i,T_i(1\leX_i,Y_i,T_i\le10^9)\),表示问在\(T_i\)......
  • 物联网工程师技术之C语言IO输入输出技术
    本章重点​语句和语句块​printf函数​scanf函数在C语言编程中,经常需要通过输入设备(如键盘)向程序录入信息,或者将信息显示在输出设备(如屏幕),这时,可以使用输入输出语句来完成。输入输出语句是用户与程序交互的唯一途径,掌握好输入输出语句对后面的学习至关重要。本章将针对输入......
  • 无涯教程-Scala - while 循环函数
    在给定条件为真时重复一个语句或一组语句,它在执行循环体之前判断条件,只要给定条件为真,while循环语句就会重复执行目标语句。while-语法以下是while循环的语法。while(condition){statement(s);}while-流程图while-示例objectDemo{defmain(args:Array[......
  • linux 安装 elasticsearch
    安装Elasticsearch在Linux系统上的步骤如下:首先,确保你的系统已经安装了Java,Elasticsearch是基于Java开发的,所以需要先安装Java运行环境。可以使用以下命令来检查Java是否已经安装:java-version如果Java已经安装,将会显示Java版本信息。如果没有安装,请根据你的系统......
  • 【小记】Docker容器间SSH公钥自动交换实现免密登录的一次尝试
    咋想到这茬了最近开始忙毕设的事儿了,想部署个伪分布式的Spark+Hadoop集群来进行测试。思来考去,最终咱把目光放在了Docker上。盘了两天,发现这玩意意外的有趣,镜像构建好后开箱即用,省去了些配置环境的成本。不过呢,在配置Hadoop的时候我发现了一个问题——Hadoop分布式搭建要求各......
  • VScode 扩展推荐和配置
    VScode扩展推荐和配置VSCodeExtensions推荐ThemesDraculaOfficial拥有明亮的颜色和舒适的对比度,非常适合长时间编程。Nord基于北极地区自然色调的冷色调主题,提供清晰和舒适的视觉体验。CarbonProductIcons图标设计好看GitHubPlusThemeOneDarkPro基于Atom编......