CommunityToolkit.Mvvm学习笔记(5)——ObservableValidator
创始人
2024-05-24 14:34:15
0

文章目录

  • 一、引言
  • 二、ObservableValidator
    • 2.1. 它是如何工作的?
    • 2.2. 简单属性
    • 2.3. 自定义验证方法
    • 2.4. 自定义验证attribute
  • 三、结尾


一、引言

很久没用WPF了,最近有个桌面端的项目,所以又回来使用了。

MVVM工具包这个系列,我之前写了1、2、3、4、6、7节,第5节的内容跳过了,这次补上。

第5节的内容是验证(validation)相关的,上次看到时还是初学,内容没看太懂,快速过一遍官方文档,有点囫囵吞枣的意思。

这次回过头来学呢,是因为我需要实现一个功能,又因为我当时瞄了一眼这节内容是与验证相关的,还有点印象,所以我直接就决定写篇博客,系统学习下。

我要实现的功能很常见,就是过滤掉一些前端的错误输入,然后给出一定反馈。

废话不多说,直接进入主题。

二、ObservableValidator

这节的标题—— ObservableValidator (可监视的验证器),一看这个单词就知道,它与ObservableObject有关,能监视到属性值更新,并且与验证有关。

ObservableValidator是实现了 INotifyDataErrorInfo 接口的基类,为验证暴露给其他程序模块的属性提供支持。它也继承自 ObservableObject ,所以它也实现了 INotifyPropertyChangedINotifyPropertyChanging 。它可用作需要同时支持属性更改通知和属性验证的所有类型的起点
在这里插入图片描述

它继承自ObservableObject意味着它是个自动通知类。
实现了 INotifyDataErrorInfo 接口:
这里的接口是代码层面的接口,而非更高层的模块间的接口。代码上的接口意味着,继承它的类都要实现这个接口,意味着这些类需要有这个功能(尽管实现起来各异)。就从这个接口的名称可以看出来,它是用于通知数据错误信息的,很明显,与验证前端输入这点非常符合。
总之,若一个类继承了 ObservableValidator ,那就说明该类存在一些属性,希望进行验证。

2.1. 它是如何工作的?

ObservableValidator 有以下主要特性:

  • 提供了 INotifyDataErrorInfo 的基本实现,暴露了 ErrorsChanged 事件和其他必要的api。
  • 提供了一系列额外的 SetProperty 重载(在基本 ObservableObject 类提供的重载之上),这些重载提供了在更新值之前自动验证属性和引发必要事件的能力。
  • 暴露了许多 TrySetProperty 重载,这些重载与 SetProperty 类似,但只有在验证成功时才能更新目标属性,并返回生成的错误(若有)以供进一步检查。
  • 还暴露了 ValidateProperty 方法,如果某个属性值没有更新,但它的验证依赖于已更新的另一个属性的值,那么该方法对于手动触发特定属性的验证非常有用。
  • 暴露了 ValidateAllProperties 方法,该方法自动执行当前实例中所有公有实例属性的验证,前提是它们至少有一个[ValidationAttribute]。
  • 暴露了一个 ClearAllErrors 方法,该方法在重置绑定到用户可能想要再次填写的表单的model时非常有用。
  • 还提供了许多构造函数,允许传递不同的参数来初始化 ValidationContext 实例,该实例将用于验证属性。当使用可能需要额外服务或选项才能正常工作的自定义验证属性时,这尤其有用。

2.2. 简单属性

简单属性就是单个的属性,不是复合的。

下面有个例子,演示了如何实现一个同时支持更改通知和验证的属性:

public class RegistrationForm : ObservableValidator
{private string name;[Required][MinLength(2)][MaxLength(100)]public string Name{get => name;set => SetProperty(ref name, value, true);}
}

这里调用了 ObservableValidator 暴露的 SetProperty(ref T, T, bool, string) 方法,
在这里插入图片描述

额外参数 bool 设置为 true 时表示在属性更新时会验证该属性。ObservableValidator 将自动在每个新值(属性上方应用了attribute指定的)上进行验证。接着,其他组件(如UI控件)可以与viewmodel交互,并修改状态来反映当前存在于viewmodel中的错误,方法是通过注册到 ErrorsChanged 并使用 GetErrors(string) 方法检索已被修改的每个属性的错误列表。

2.3. 自定义验证方法

有时验证一个属性需要viewmodel去访问其他的服务、数据或API。这边提供了多种方法向属性添加自定义验证,使用哪种方法取决于场景和灵活度需求。下面有个示例,它说明如何使用 [CustomValidationAttribute] 类型来指示调用特定方法进行属性的额外验证:

public class RegistrationForm : ObservableValidator
{private readonly IFancyService service;public RegistrationForm(IFancyService service){this.service = service;}private string name;[Required][MinLength(2)][MaxLength(100)][CustomValidation(typeof(RegistrationForm), nameof(ValidateName))]public string Name{get => this.name;set => SetProperty(ref this.name, value, true);}public static ValidationResult ValidateName(string name, ValidationContext context){RegistrationForm instance = (RegistrationForm)context.ObjectInstance;bool isValid = instance.service.Validate(name);if (isValid){return ValidationResult.Success;}return new("The name was not validated by the fancy service");}
}

在本例中,有一个静态的 ValidateName 方法,它通过注入到viewmodel中的服务对 Name 属性执行验证(依赖注入章节有介绍)。该方法接收 name 属性值和使用中的 ValidationContext 实例为参数,其中包含viewmodel实例、正在验证的属性的名称、可选的服务提供者和一些我们使用或设置的自定义标志。在本例中,我们从 validation 上下文中检索 RegistrationForm 实例,然后从那使用注入的服务来验证属性。注意,该验证被执行,在其他attribute中指定的验证之后,所以我们可以自由组合自定义验证方法和现有的验证attribute。

上面代码示例中的Name属性上有一串 [] attribute,验证可以一个个排下去执行。

2.4. 自定义验证attribute

另一种自定义验证的方式就是实现一个自定义的 [ValidationAttribute] ,然后将验证逻辑插入重写的 IsValid 方法中。与上面的方法相比,这提供了额外的灵活性,因为它可以很容易地在多个地方重用相同的attribute。

假设我们希望根据属性关于同一viewmodel中的另一个属性的相对值来验证一个属性。首先应该定义一个自定义 [GreaterThanAttribute] ,如下所示:

public sealed class GreaterThanAttribute : ValidationAttribute
{public GreaterThanAttribute(string propertyName){PropertyName = propertyName;}public string PropertyName { get; }protected override ValidationResult IsValid(object value, ValidationContext validationContext){objectinstance = validationContext.ObjectInstance,otherValue = instance.GetType().GetProperty(PropertyName).GetValue(instance);if (((IComparable)value).CompareTo(otherValue) > 0){return ValidationResult.Success;}return new("The current value is smaller than the other one");}
}

现在,我们可以将该attribute添加到viewmodel中了:

public class ComparableModel : ObservableValidator
{private int a;[Range(10, 100)][GreaterThan(nameof(B))]public int A{get => this.a;set => SetProperty(ref this.a, value, true);}private int b;[Range(20, 80)]public int B{get => this.b;set{SetProperty(ref this.b, value, true);ValidateProperty(A, nameof(A));}}
}

本例中,有两个数字属性,它们必须在指定范围内,并且彼此间具有特定的关系(A需要大于B)。我们已经在第一个属性上添加了新的 [GreaterThanAttribute] ,并且在B的Setter中添加了对 ValidateProperty 的调用,这样每当B改变时,a都会再次被验证(因为它的验证依赖于b)。我们只需要viewmodel中的这两行代码来启动这个自定义验证,并且我们还获得了一个可重用的自定义验证attribute的好处,这个attribute在应用程序的其他viewmodel中也有用。这种方法还有助于代码模块化,因为验证逻辑现在完全与viewmodel定义本身解耦了。

三、结尾

验证器的代码并不复杂,上面几小节中的示例直接拷贝出来改一改就能起作用,相当于是对 ObservableObject 的扩展。

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...