手写 Attribute + Reflection 验证框架

目标:
像 [Required][MaxLength] 一样,通过 Attribute 声明验证规则,
通过 Reflection 自动执行验证逻辑,彻底解耦业务代码。

一、先看最终使用效果(非常重要)

我们希望业务代码只长这样👇

 /// <summary>
 /// 用户数据传输对象(DTO)
 /// 用于封装用户基础信息,并通过特性标注数据验证规则
 /// </summary>
 public class UserDto
 {
     // 用户名 - 必填验证(为空时提示指定错误信息)
     [Required(ErrorMessage = "用户名不能为空")]
     // 用户名 - 最大长度验证(超过10字符时提示指定错误信息)
     [MaxLength(10, ErrorMessage = "用户名不能超过10个字符")]
     public required string UserName { get; set; }

     // 年龄 - 范围验证(必须在18~60之间,否则提示指定错误信息)
     [Range(18, 60, ErrorMessage = "年龄必须在18~60之间")]
     public int Age { get; set; }
 }

调用:

    // 1. 创建用户数据传输对象(DTO)实例
    var user = new UserDto
    {
        UserName = "",  // 用户名设为空字符串(用于测试验证规则)
        Age = 10        // 设置年龄为10
    };

    // 2. 调用验证器验证用户DTO的合法性
    var result = Validator.Validate(user);

    // 3. 判断验证结果:如果验证不通过
    if (!result.IsValid)
    {
        // 遍历所有验证错误并输出到控制台
        foreach (var error in result.Errors)
        {
            Console.WriteLine(error);
        }
    }
}

返回结果:

用户名不能为空
年龄必须在18~60之间

👉 没有 if / else
👉 没有侵入业务代码
👉 规则完全由 Attribute 声明

二、整体架构设计(先理解,不要急着写)

1️⃣ 核心设计思想

Attribute:声明规则
Validator:扫描规则
Rule:执行规则
1️⃣ 所有验证特性的基类

2️⃣ 框架结构

00009.手写 Attribute + Reflection 验证框架
│
├── Attributes
│   ├── ValidationAttribute.cs   // 验证基类
│   ├── RequiredAttribute.cs
│   ├── MaxLengthAttribute.cs
│   └── RangeAttribute.cs
│
├── Core
│   ├── ValidationResult.cs
│   └── Validator.cs             // 核心引擎

三、第一步:定义验证 Attribute 的“统一抽象”

1️⃣ 所有验证特性的基类

namespace ConsoleApp1.Attributes
{
    /// <summary>
    /// 验证特性抽象基类
    /// 所有自定义验证特性(如Required/Range等)的父类,继承Attribute使其可作为特性标记
    /// </summary>
    public abstract class ValidationAttribute : Attribute
    {
        /// <summary>
        /// 验证失败时的自定义错误提示信息
        /// </summary>
        public required string ErrorMessage { get; set; }

        /// <summary>
        /// 抽象验证方法(核心逻辑)
        /// 由子类实现具体的验证规则,判断传入值是否符合要求
        /// </summary>
        /// <param name="value">待验证的属性值</param>
        /// <returns>验证是否通过(true=有效,false=无效)</returns>
        public abstract bool IsValid(object value);
    }
}

设计要点:

  • ✔ Attribute 只描述规则
  • ✔ 不关心属性名、对象
  • ✔ 只判断“值是否合法”

四、第二步:实现具体验证规则 Attribute

1️⃣ Required(必填)

namespace ConsoleApp1.Attributes
{
    /// <summary>
    /// 必填验证特性
    /// 验证属性值是否非空(字符串需额外验证非空白)
    /// </summary>
    public class RequiredAttribute : ValidationAttribute
    {
        /// <summary>
        /// 重写抽象验证方法,实现必填验证规则
        /// </summary>
        /// <param name="value">待验证的属性值</param>
        /// <returns>true=值有效(非空/非空白),false=值无效</returns>
        public override bool IsValid(object value)
        {
            // 规则1:值为null直接验证失败
            if (value == null) return false;
            // 规则2:字符串类型需验证非空白(空字符串/全空格都算无效)
            if (value is string str) return !string.IsNullOrWhiteSpace(str);
            // 规则3:非字符串且非null的类型(如int/long)默认验证通过
            return true;
        }
    }
}

2️⃣ MaxLength(字符串长度)

namespace ConsoleApp1.Attributes
{
    /// <summary>
    /// 最大长度验证特性
    /// 验证属性值(转换为字符串后)的长度不超过指定最大值
    /// </summary>
    public class MaxLengthAttribute : ValidationAttribute
    {
        // 最大长度阈值(只读,通过构造函数初始化)
        private readonly int _maxLength;

        /// <summary>
        /// 构造函数:初始化最大长度验证的阈值
        /// </summary>
        /// <param name="maxLength">允许的最大字符长度</param>
        public MaxLengthAttribute(int maxLength)
        {
            _maxLength = maxLength;
        }

        /// <summary>
        /// 重写抽象验证方法,实现最大长度验证规则
        /// </summary>
        /// <param name="value">待验证的属性值</param>
        /// <returns>true=值长度≤阈值,false=值长度超出阈值</returns>
        public override bool IsValid(object value)
        {
            // 规则1:值为null时默认验证通过(必填校验由RequiredAttribute单独处理)
            if (value == null) return true;
            // 规则2:将值转为字符串,验证其长度是否≤最大长度阈值
            return value.ToString().Length <= _maxLength;
        }
    }
}

3️⃣ Range(数值区间)

public class RangeAttribute : ValidationAttribute
{
    private readonly int _min;
    private readonly int _max;

    public RangeAttribute(int min, int max)
    {
        _min = min;
        _max = max;
    }

    public override bool IsValid(object value)
    {
        if (value == null) return true;
        int intValue = Convert.ToInt32(value);
        return intValue >= _min && intValue <= _max;
    }
}

五、第三步:核心引擎 —— Validator(重点)

这一步是整个框架的灵魂.

1️⃣ 验证结果模型

namespace ConsoleApp1.Core
{
    /// <summary>
    /// 验证结果封装类
    /// 用于存储验证过程中的错误信息,并标识整体验证是否通过
    /// </summary>
    public class ValidationResult
    {
        /// <summary>
        /// 验证是否通过(只读)
        /// 错误集合为空时表示验证通过,否则未通过
        /// </summary>
        public bool IsValid => Errors.Count == 0;

        /// <summary>
        /// 验证错误信息集合
        /// 初始化时创建空列表,避免空引用
        /// </summary>
        public List<string> Errors { get; } = new();
    }
}

2️⃣ Validator 核心实现(Reflection 扫描)

// 引入自定义验证特性基类
using ConsoleApp1.Attributes;
using System.Reflection;

namespace ConsoleApp1.Core
{
    /// <summary>
    /// 通用数据验证器(静态类)
    /// 基于反射+自定义验证特性,实现对象属性的通用验证逻辑
    /// </summary>
    public static class Validator
    {
        /// <summary>
        /// 验证指定对象的所有带验证特性的属性
        /// </summary>
        /// <param name="obj">待验证的对象</param>
        /// <returns>验证结果(包含错误信息集合)</returns>
        public static ValidationResult Validate(object obj)
        {
            // 初始化验证结果对象(用于存储错误信息)
            var result = new ValidationResult();

            // 边界校验:验证对象为空时直接添加错误并返回
            if (obj == null)
            {
                result.Errors.Add("验证对象不能为空");
                return result;
            }

            // 获取对象的类型信息(用于反射解析属性)
            Type type = obj.GetType();

            // 遍历对象的所有公共属性
            foreach (PropertyInfo prop in type.GetProperties())
            {
                // 获取当前属性的实际值
                object value = prop.GetValue(obj);

                // 获取当前属性上所有自定义验证特性
                var attributes = prop.GetCustomAttributes<ValidationAttribute>();

                // 遍历每个验证特性,执行具体验证逻辑
                foreach (var attr in attributes)
                {
                    // 若验证不通过,收集错误信息
                    if (!attr.IsValid(value))
                    {
                        // 优先使用特性自定义错误信息,无则使用默认提示
                        string error = attr.ErrorMessage ?? $"{prop.Name} 验证失败";
                        result.Errors.Add(error);
                    }
                }
            }

            // 返回最终验证结果
            return result;
        }
    }
}

⚠️ 关键点:

  • ✔ 只反射 Property
  • ✔ 支持 多个 Attribute
  • ✔ 不关心具体规则
  • ✔ 完全开放扩展

六、完整测试示例

// 引入核心功能命名空间(包含UserDto和Validator验证器)
using ConsoleApp1.Core;

// 程序主命名空间
namespace ConsoleApp1
{
    // 程序入口类
    internal class Program
    {
        // 程序主入口方法
        static void Main(string[] args)
        {
            // 1. 创建用户数据传输对象(DTO)实例
            var user = new UserDto
            {
                UserName = "",  // 用户名设为空字符串(用于测试验证规则)
                Age = 10        // 设置年龄为10
            };

            // 2. 调用验证器验证用户DTO的合法性
            var result = Validator.Validate(user);

            // 3. 判断验证结果:如果验证不通过
            if (!result.IsValid)
            {
                // 遍历所有验证错误并输出到控制台
                foreach (var error in result.Errors)
                {
                    Console.WriteLine(error);
                }
            }
        }
    }
}

输出:

用户名不能为空
年龄必须在18~60之间

七、进阶一:支持“属性名 + 错误消息”

增强 ValidationResult

public class ValidationError
{
    public string PropertyName { get; set; }
    public string Message { get; set; }
}

这样可以:

  • 支持前端字段映射
  • 支持 JSON 返回

👉 这就是 ASP.NET Core ModelState 的雏形

八、进阶二:短路验证(失败即停)

foreach (var attr in attributes)
{
    if (!attr.IsValid(value))
    {
        result.Errors.Add(attr.ErrorMessage);
        break; // 短路
    }
}

九、进阶三:缓存反射结果(生产级)

static readonly Dictionary<Type, PropertyInfo[]> _cache = new();

PropertyInfo[] properties = _cache.TryGetValue(type, out var props)
    ? props
    : _cache[type] = type.GetProperties();

👉 真正慢的不是 Attribute,是重复反射

十、你已经“无缝理解” ASP.NET Core 验证体系了

你刚才写的,其实就是:

你写的ASP.NET Core
ValidationAttributeValidationAttribute
ValidatorObjectModelValidator
ValidateModel Binding + Validation
ValidationResultModelState

十一、终极总结(框架级认知)

Attribute 是规则的“声明语言”
Reflection 是规则的“发现机制”
Validator 是规则的“执行引擎”

当你能手写出这一套时,说明你已经:

  • 真正理解 Attribute 的价值
  • 理解 Reflection 的正确用法
  • 具备设计“声明式框架”的能力
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcome_com

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值