83 lines
3.6 KiB
C#
83 lines
3.6 KiB
C#
|
|
using FluentValidation;
|
|||
|
|
using System.Globalization;
|
|||
|
|
using System.Windows;
|
|||
|
|
using System.Windows.Controls;
|
|||
|
|
using System.Windows.Data;
|
|||
|
|
using ValidationResult = System.Windows.Controls.ValidationResult;
|
|||
|
|
|
|||
|
|
namespace YY.Admin.Core.FluentValidation
|
|||
|
|
{
|
|||
|
|
internal class FluentAutoValidationRule<T> : ValidationRule, IFluentAutoValidationRule where T : class
|
|||
|
|
{
|
|||
|
|
public IValidator<T>? Validator { get; set; }
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 当前绑定控件对应的属性名
|
|||
|
|
/// </summary>
|
|||
|
|
public string? PropertyName { get; set; }
|
|||
|
|
|
|||
|
|
// 👇 ValidationStep.UpdatedValue为true: 让Validate方法中value参数为BindingExpression类型,而非值类型
|
|||
|
|
public FluentAutoValidationRule() : base(ValidationStep.UpdatedValue, true) { }
|
|||
|
|
|
|||
|
|
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
|
|||
|
|
{
|
|||
|
|
if (Validator == null || string.IsNullOrEmpty(PropertyName))
|
|||
|
|
return ValidationResult.ValidResult;
|
|||
|
|
|
|||
|
|
// 如果 value 不是 BindingExpression,先尝试判断是否是 BindingGroup (兼容性保护)
|
|||
|
|
if (value is not BindingExpression bindingExpr)
|
|||
|
|
{
|
|||
|
|
// 有时 WPF 在不同阶段会传入 BindingGroup 或其他类型,直接跳过初始化阶段的验证,避免误报
|
|||
|
|
return ValidationResult.ValidResult;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- 更可靠地判断“初始化阶段的第一次验证” ---
|
|||
|
|
// 如果绑定目标尚未挂到视觉树或控件未 Loaded,则视为初始化,不执行验证
|
|||
|
|
var target = bindingExpr.Target as DependencyObject;
|
|||
|
|
if (target != null)
|
|||
|
|
{
|
|||
|
|
// 1) 如果是 FrameworkElement 并且还没 Loaded,则跳过
|
|||
|
|
if (target is FrameworkElement fe && !fe.IsLoaded)
|
|||
|
|
return ValidationResult.ValidResult;
|
|||
|
|
|
|||
|
|
// 2) 如果 PresentationSource 为空,也说明还没连接到可视树,跳过
|
|||
|
|
var ps = System.Windows.PresentationSource.FromVisual(target as System.Windows.Media.Visual);
|
|||
|
|
if (ps == null)
|
|||
|
|
return ValidationResult.ValidResult;
|
|||
|
|
|
|||
|
|
// 3) 另外,如果控件不可见,也可能是初始化阶段,可根据需要跳过
|
|||
|
|
if (target is UIElement ui && !ui.IsVisible)
|
|||
|
|
return ValidationResult.ValidResult;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var dataItem = bindingExpr.DataItem;
|
|||
|
|
if (dataItem == null)
|
|||
|
|
return new ValidationResult(false, "绑定源为空");
|
|||
|
|
|
|||
|
|
// 解析 Path,例如 "SysUser.Account"
|
|||
|
|
var path = bindingExpr.ParentBinding.Path?.Path ?? string.Empty;
|
|||
|
|
var rootPropName = path.Split('.')[0]; // SysUser
|
|||
|
|
var modelProp = dataItem.GetType().GetProperty(rootPropName);
|
|||
|
|
if (modelProp == null)
|
|||
|
|
return ValidationResult.ValidResult;
|
|||
|
|
|
|||
|
|
var modelObj = modelProp.GetValue(dataItem);
|
|||
|
|
if (modelObj is not T model)
|
|||
|
|
return ValidationResult.ValidResult;
|
|||
|
|
|
|||
|
|
// 调用 FluentValidation 验证指定属性
|
|||
|
|
var result = Validator.ValidateAsync(model, opts => opts.IncludeProperties(PropertyName))
|
|||
|
|
.ConfigureAwait(false) // 同步等待Task(ValidationRule 不支持 async → 只能同步等待。)
|
|||
|
|
.GetAwaiter()
|
|||
|
|
.GetResult();
|
|||
|
|
var error = result.Errors.FirstOrDefault(e => e.PropertyName == PropertyName);
|
|||
|
|
|
|||
|
|
if (error != null)
|
|||
|
|
return new ValidationResult(false, error.ErrorMessage);
|
|||
|
|
|
|||
|
|
return ValidationResult.ValidResult;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|