From b90463fe0bb06c04ee8d45a21fabd94c93dbac3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=96=B5=E4=BD=A0=E4=B8=AA=E6=B1=AA=E5=91=80?= Date: Tue, 12 Aug 2025 22:15:16 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(=E6=9D=A1=E4=BB=B6=E5=BF=85?= =?UTF-8?q?=E5=A1=AB=E5=8F=82=E6=95=B0=E9=AA=8C=E8=AF=81):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=9D=A1=E4=BB=B6=E5=BF=85=E5=A1=AB=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=89=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Attribute/RequiredIFAttribute.cs | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 Admin.NET/Admin.NET.Core/Attribute/RequiredIFAttribute.cs diff --git a/Admin.NET/Admin.NET.Core/Attribute/RequiredIFAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/RequiredIFAttribute.cs new file mode 100644 index 00000000..399707de --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/RequiredIFAttribute.cs @@ -0,0 +1,216 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 条件必填参数验证特性(支持多条件判断) +/// 当另一个属性的值满足指定条件时,验证当前属性是否非空。 +/// +/// +/// // 实例1 +/// [RequiredIF(nameof(TarProperty), 1, ErrorMessage = "TarProperty为1时,SomeProperty不能为空")]
+/// public string SomeProperty { get; set; }
+///
+/// +/// // 实例2 +/// [RequiredIF(nameof(TarProperty), 1, Operator.NotEqual, ErrorMessage = "TarProperty不为1时,SomeProperty不能为空")]
+/// public string SomeProperty { get; set; }
+///
+/// +/// // 实例3 +/// [RequiredIF(nameof(TarProperty), new[]{ 1, 2 }, Operator.Contains, ErrorMessage = "TarProperty包含为1或2时,SomeProperty不能为空")]
+/// public string SomeProperty { get; set; }
+///
+/// +/// // 实例4 +/// [RequiredIF(nameof(TarProperty), new[]{ 1, 2 }, Operator.NotContains, ErrorMessage = "TarProperty不包含1和2时,SomeProperty不能为空")]
+/// public string SomeProperty { get; set; }
+///
+[AttributeUsage(AttributeTargets.Property)] +public sealed class RequiredIFAttribute : ValidationAttribute +{ + /// + /// 依赖的属性名称 + /// + private string PropertyName { get; set; } + + /// + /// 目标比较值 + /// + private object TargetValue { get; set; } + + /// + /// 比较运算符 + /// + private Operator Comparison { get; set; } + + public RequiredIFAttribute(string propertyName, object targetValue = null, Operator comparison = Operator.Equal) + { + PropertyName = propertyName; + TargetValue = targetValue; + Comparison = comparison; + } + + /// + /// 验证属性值是否符合要求 + /// + /// 当前属性的值 + /// 验证上下文 + /// 验证结果 + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + ArgumentNullException.ThrowIfNull(validationContext); + + var instance = validationContext.ObjectInstance; + var targetProperty = instance.GetType().GetProperty(PropertyName); + + if (targetProperty == null) return new ValidationResult($"找不到属性: {PropertyName}"); + var targetValue = targetProperty.GetValue(instance); + + if (!ShouldValidate(targetValue)) + { + return ValidationResult.Success; + } + + return IsValueEmpty(value) + ? new ValidationResult(ErrorMessage ?? $"{validationContext.MemberName}不能为空") + : ValidationResult.Success; + } + + /// + /// 判断是否需要进行验证 + /// + /// 依赖属性的值 + /// 是否需要验证 + private bool ShouldValidate(object targetValue) + { + if (TargetValue == null) return targetValue == null || (targetValue is string str && string.IsNullOrWhiteSpace(str)) || targetValue is long and 0; + + return TargetValue is IEnumerable enumerable and not string + ? enumerable.Cast().Any(item => CompareValues(targetValue, item, Operator.Equal)) + : CompareValues(targetValue, TargetValue, Comparison); + } + + /// + /// 检查值是否为空 + /// + /// 要检查的值 + /// 值是否为空 + private static bool IsValueEmpty(object value) + { + return value == null || (value is string str && string.IsNullOrWhiteSpace(str)) || value is long and 0; + } + + /// + /// 比较两个值 + /// + /// 源值 + /// 目标值 + /// 比较运算符 + /// 比较结果 + private static bool CompareValues(object sourceValue, object targetValue, Operator comparison) + { + // 处理null值比较 + if (sourceValue == null || targetValue == null) + { + return comparison switch + { + Operator.Equal => sourceValue == targetValue, + Operator.NotEqual => sourceValue != targetValue, + _ => false + }; + } + + // 处理集合包含操作 + if (comparison is Operator.Contains or Operator.NotContains) + { + if (targetValue is not IEnumerable enumerable) + { + return false; + } + + bool contains = enumerable.Cast().Any(item => item != null && item.Equals(sourceValue)); + return comparison == Operator.Contains ? contains : !contains; + } + + // 处理可比较类型 + if (sourceValue is IComparable sourceComparable && targetValue is IComparable targetComparable) + { + int result = sourceComparable.CompareTo(targetComparable); + return comparison switch + { + Operator.Equal => result == 0, + Operator.NotEqual => result != 0, + Operator.GreaterThan => result > 0, + Operator.LessThan => result < 0, + Operator.GreaterThanOrEqual => result >= 0, + Operator.LessThanOrEqual => result <= 0, + _ => false + }; + } + + // 默认比较 + bool equals = sourceValue.Equals(targetValue); + return comparison == Operator.Equal ? equals : !equals; + } +} + +/// +/// 比较运算符枚举 +/// +[SuppressSniffer] +[Description("比较运算符枚举")] +public enum Operator +{ + /// + /// 等于 (==) + /// + [Description("等于")] + Equal, + + /// + /// 不等于 (!=) + /// + [Description("不等于")] + NotEqual, + + /// + /// 大于 (>) + /// + [Description("大于")] + GreaterThan, + + /// + /// 小于 (<) + /// + [Description("小于")] + LessThan, + + /// + /// 大于等于 (>=) + /// + [Description("大于等于")] + GreaterThanOrEqual, + + /// + /// 小于等于 (<=) + /// + [Description("小于等于")] + LessThanOrEqual, + + /// + /// 包含 (包含于集合) + /// + [Description("包含")] + Contains, + + /// + /// 不包含 (不包含于集合) + /// + [Description("不包含")] + NotContains +} \ No newline at end of file