65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 FluentValidation 进行动态验证 - 替代方案

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (2投票s)

2016 年 1 月 10 日

CPOL

3分钟阅读

viewsIcon

12827

downloadIcon

194

这是“使用 FluentValidation 在 WPF/MVVM 中进行动态验证”的替代方案

引言

FluentValidation 似乎是在 Wpf 中处理 ValidationRules 的一种非常方便的方式。

我真的推荐阅读 tetsushmz 撰写的 原始文章,其中提供了很好的介绍。

应用摘要

在单独的项目中有一个名为 "Problem5" 的 Viewmodel(一个数学难题,参见 Project Euler..net 上的 Problem5),用户可以在其中输入两个文本,然后触发一个命令来计算结果。

验证这些文本并非易事,而 FluentValidation 在这方面做得很好。

而且 View 演示了如何自动标记无效的文本框,以及在出现错误时按钮如何自动被禁用。

此外,MainViewmodel 提供了 Problem5 实例所有无效属性的错误消息列表。

我所做的不同之处

总的来说,我保留了原始解决方案,但我简化了用法,我能够删除一个接口,并且减少了使 FluentValidations 正常工作所需的成员数量。

以下是使用要求,如 tetsushmz 的方法所示

public class Problem5 : ValidatableBindableBase, INotifyDataErrorInfo {

   private readonly IValidator<Problem5> validator;

   public IEnumerable GetErrors(string propertyName) {
      return this.validator.GetErrors(propertyName);
   }

   public IList<string> GetAllErrors() {
      return this.validator.GetAllErrors();
   }

   public override void ValidateAllProperties() {
      this.validator.Validate(this);
   }

   public Problem5(IValidator<Problem5> validator) {
      this.validator = validator;
      this.validator.ErrorsChanged += (s, e) => this.OnErrorsChanged(e);
      // ...
  1. ValidatableBindableBase 继承,实现 INotifyDataErrorInfo
  2. 声明一个 validator 对象
  3. 实现 INotifyDataErrorInfo.GetErrors(),并将其重定向到 validator
  4. 实现 INotifyDataErrorInfo.GetAllErrors(),并将其重定向到 validator
  5. 重写 ValidatableBindableBase.ValidateAllProperties(),并将其重定向到 validator
  6. 通过构造函数注入 validator 对象(并存储它)
  7. 订阅 IValidator.ErrorsChanged 事件,以调用 ValidatableBindableBase.OnErrorsChanged() 方法

现在是我的简化

public class Problem5 : FluentNotifyDataErrorInfo {

   private readonly AbstractValidator<Problem5> validator;

   protected override ValidationResult Validate() { return this.validator.Validate(this); }

   public Problem5(AbstractValidator<Problem5> validator) {
      this.validator = validator;
      //...
  1. FluentNotifyDataErrorInfo 继承
  2. 声明一个 validator 对象
  3. 重写 FluentNotifyDataErrorInfo.Validate(),并将其重定向到 validator
  4. 通过构造函数注入 validator 对象(并存储它)

内存占用

通常验证涉及许多对象,甚至大量数据。 所以保持验证的内存占用较小是一个好习惯。

IValidator<Problem5> 的原始具体实现中,tetsushmz 为每个数据项分配了自己的 Dictionary 和自己的 Problem5Validator,这是一个包含所有验证规则的对象。

这不一定需要 ;-) 。

我的实现将相同的 Problem5Validator 实例广播到所有数据项,并且我的 FluentNotifyDataErrorInfo(数据项基类)仅在存在错误时才包含一个错误字典 - 否则它被设置为一个静态的空 Dictionary

我的替代方案的缺点

  • 我的数据项程序集需要引用 FluentValidation 库。 我决定放弃原始方案的解耦 IValidator<T> 接口,以使事情更简单。
    由于无论数据项程序集是否引用它,在进行 fluent-validated 应用时,无论如何都需要该库。
  • 某些场景可能需要每个数据项持有自己的验证规则集。 我无法想象这样的场景,但我必须接受这种可能性。

旁注:差异分析算法

要掌握的一个(小)挑战是,将先前的错误列表与较新版本进行比较,以检测哪些错误消失了,哪些保持不变,哪些发生了变化,以及哪些是新的。

差异分析是一种通用模式,这里有一个解决方案,可以高效地解决这个问题

  1. 将 list1 转换为字典或哈希集 - 命名为 dic1
  2. 然后循环 list2,并尝试从 dic1 中删除每个 item2。 哈希集/字典的 Remove() 函数如果该项不存在则返回 False
  3. 如果 True:两个项目相同 - 意味着:该项目未更改
  4. 如果 False:那个 item2 是新的
  5. 在循环完所有 item2 之后,dic1 中剩余的项目(如果存在)是被删除的项目 - 因为它们在 list2 中不存在。

此方法旨在适应当前场景 - 如果您愿意,请查看我的 FluentNotifyDataErrorInfo.ProcessChangedErrors() 方法

private void ProcessChangedErrors(Dictionary<string, string> updatedErrors) {
   var oldErrs = this._Errors;
   _Errors = updatedErrors;
   foreach (var kvp in updatedErrors) {
      string oldErr;
      if (oldErrs.TryGetValue(kvp.Key, out oldErr)) {
         oldErrs.Remove(kvp.Key);
         if (oldErr == kvp.Value) continue;
      }
      // raise on new error or on changed error-message
      ErrorsChanged.Raise(this, new DataErrorsChangedEventArgs(kvp.Key));
   }
   // raise, if old error is vanished
   foreach (var oldErrKey in oldErrs.Keys) 
      ErrorsChanged.Raise(this, new DataErrorsChangedEventArgs(oldErrKey));
}

它的工作是为每个属性引发 INotifyDataErrorInfo.ErrorsChanged 事件,该属性的错误的存在(或仅错误消息)发生了变化。
我不得不更改上述方法中的细节,即我使用 Dictionary.TryGetValue() 而不是 Dictionary.Remove(),因为需要进行额外的检查来检测它是否已更改(第 8 行)。

结论

有些人可能认为我是一个令人不快的聪明人,我不确定我是否能令人信服地拒绝这一点 ;-) 。 但这真的重要吗? 如果我令人不快的属性有助于您更好地使用验证,那么它也不是完全不值得的,是吗?

而且我真的认为,检查不同的方法,它们可以达到相同的结果,这对于找出有用的模式、功能、灵活性、优缺点等等非常有帮助。

使用 FluentValidation 进行动态验证 - 替代方案 - CodeProject - 代码之家
© . All rights reserved.