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






4.50/5 (2投票s)
这是“使用 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);
// ...
- 从
ValidatableBindableBase
继承,实现INotifyDataErrorInfo
- 声明一个
validator
对象 - 实现
INotifyDataErrorInfo.GetErrors()
,并将其重定向到validator
- 实现
INotifyDataErrorInfo.GetAllErrors()
,并将其重定向到validator
- 重写
ValidatableBindableBase.ValidateAllProperties()
,并将其重定向到validator
- 通过构造函数注入
validator
对象(并存储它) - 订阅
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;
//...
- 从
FluentNotifyDataErrorInfo
继承 - 声明一个
validator
对象 - 重写
FluentNotifyDataErrorInfo.Validate()
,并将其重定向到validator
- 通过构造函数注入
validator
对象(并存储它)
内存占用
通常验证涉及许多对象,甚至大量数据。 所以保持验证的内存占用较小是一个好习惯。
在 IValidator<Problem5>
的原始具体实现中,tetsushmz 为每个数据项分配了自己的 Dictionary
和自己的 Problem5Validator
,这是一个包含所有验证规则的对象。
这不一定需要 ;-) 。
我的实现将相同的 Problem5Validator
实例广播到所有数据项,并且我的 FluentNotifyDataErrorInfo
(数据项基类)仅在存在错误时才包含一个错误字典 - 否则它被设置为一个静态的空 Dictionary
。
我的替代方案的缺点
- 我的数据项程序集需要引用 FluentValidation 库。 我决定放弃原始方案的解耦
IValidator<T>
接口,以使事情更简单。
由于无论数据项程序集是否引用它,在进行 fluent-validated 应用时,无论如何都需要该库。 - 某些场景可能需要每个数据项持有自己的验证规则集。 我无法想象这样的场景,但我必须接受这种可能性。
旁注:差异分析算法
要掌握的一个(小)挑战是,将先前的错误列表与较新版本进行比较,以检测哪些错误消失了,哪些保持不变,哪些发生了变化,以及哪些是新的。
差异分析是一种通用模式,这里有一个解决方案,可以高效地解决这个问题
- 将 list1 转换为字典或哈希集 - 命名为
dic1
。 - 然后循环 list2,并尝试从
dic1
中删除每个 item2。 哈希集/字典的Remove()
函数如果该项不存在则返回False
- 如果
True
:两个项目相同 - 意味着:该项目未更改 - 如果
False
:那个 item2 是新的 - 在循环完所有 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 行)。
结论
有些人可能认为我是一个令人不快的聪明人,我不确定我是否能令人信服地拒绝这一点 ;-) 。 但这真的重要吗? 如果我令人不快的属性有助于您更好地使用验证,那么它也不是完全不值得的,是吗?
而且我真的认为,检查不同的方法,它们可以达到相同的结果,这对于找出有用的模式、功能、灵活性、优缺点等等非常有帮助。