FluentValidation在C# WPF中的应用

这篇具有很好参考价值的文章主要介绍了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平台上。欢迎感兴趣的开发者访问以下链接获取源码:

  • gitee: https://gitee.com/dotnet9/FluentValidationForWpf
  • github: https://github.com/dotnet9/FluentValidationForWPF

7. 总结

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

参考:文章来源地址https://www.toymoban.com/news/detail-823673.html

  • FluentValidation官网: https://fluentvalidation.net/

到了这里,关于FluentValidation在C# WPF中的应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C# 介绍、应用领域、入门、语法、输出和注释详解

    C# (发音为“C-Sharp”)是一种由 Microsoft 创建的面向对象的编程语言,运行在 .NET Framework 上。源于 C 家族,与流行的语言如 C++ 和 Java 相近。首个版本发布于 2002 年,而最新版本, C# 12 ,于 2023 年 11 月发布 C# 广泛用于: 移动应用程序 桌面应用程序 Web 应用程序 Web 服务 网站

    2024年01月16日
    浏览(38)
  • C# WPF上位机开发(键盘绘图控制)

    【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】         在软件开发中,如果存在canvas图像的话,一般有几种控制方法。一种是鼠标控制;一种是键盘控制;还有一种是定时器控制。定时器控制,多常见动画、游戏、3d视频当中。而鼠标控制

    2024年02月02日
    浏览(42)
  • 掌握C#中的GUI多线程技巧:WinForms和WPF实例详解

      概述: 本指南详细解释了在C#中如何在创建控件的线程以外的线程中访问GUI。基础功能使用`Control.Invoke`(WinForms)或`Dispatcher.Invoke`(WPF),高级功能则利用`SynchronizationContext`实现线程间通信,确保代码清晰可读。 在C#中,要在创建控件的线程以外的线程访问控件,可以使用

    2024年03月19日
    浏览(35)
  • C# WPF上位机开发(Web API联调)

    【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】         很多时候,客户需要开发的不仅仅是一个上位机系统,它还有其他很多配套的系统或设备,比如物流小车、立库、数字孪生等一整套系统。这个时候,上位机系统就需要和各个子系统

    2024年01月20日
    浏览(42)
  • C#开发winform&wpf后台捕获鼠标移动事件

    做 WPF和winform的时候,可以在界面上设置鼠标移动事件来检测鼠标移动,如果项目为后期改造这样做的话改动量很大,今天通过另外一种后台调用windows api的方式进行快速捕获和触发,提高开发效率分享给大家。

    2024年02月16日
    浏览(50)
  • 基于C#语言的GUI开发,主要介绍WPF框架

    下面是几种常见基于.Net的界面开发框架,都是Microsoft提供,下面简介由GPT生成 WinForms (Windows Forms WPF (Windows Presentation Foundation) UWP (Universal Windows Platform) MAUI (Multi-platform App UI) WinForms(Windows Forms): 介绍: WinForms是由Microsoft提供的用于开发Windows桌面应用程序的UI框架。它使用简单

    2024年02月03日
    浏览(46)
  • C# WPF应用使用visual studio的安装程序类的一些坑

    否则会出现命名空间System.Configuration不存在Install的报错   var s = Context.Parameters[\\\"assemblypath\\\"].ToString() 这个里面是当前文件的路径,所以需要删除掉文件的名字才能获取目录路径 var dir = s.Substring(0, s.LastIndexOf(\\\"\\\\\\\") + 1)

    2024年02月12日
    浏览(40)
  • Python在不同领域中的应用

    Python 是一种功能强大且易于使用的编程语言,因此在各个领域都有广泛的应用。以下是 Python 在不同领域中的应用: 数据科学:Python 是数据科学家和机器学习专家的首选工具之一。它有成熟的数据分析库和工具包,如 Pandas、NumPy、SciPy 和 Matplotlib。 网络编程:Python 可以用于

    2024年02月10日
    浏览(39)
  • 深度学习在通信领域中的应用

    深度学习在通信领域中的应用 深度学习作为人工智能领域的一个热门技术,一直在探索新的应用领域。近年来,深度学习在通信领域中的应用也逐渐受到关注。通信领域需要面对各种挑战和问题,例如信道估计、信号检测、通信系统优化等等。这些问题的解决,可以大大提升

    2024年02月06日
    浏览(47)
  • 流程挖掘在医疗领域中的应用价值

    医院作为一个庞大的医疗机构,承担着诊断、治疗和护理等重要任务。每天,数以千计甚至数以万计的患者涌入医院,带来了大量的医疗需求和数据。同时,医学技术的进步和信息化的发展使得医院面临着日益增长的信息量,导致信息超载的困境。 信息超载是指医院所面临的

    2024年02月11日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包