目标:
像[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 |
|---|---|
| ValidationAttribute | ValidationAttribute |
| Validator | ObjectModelValidator |
| Validate | Model Binding + Validation |
| ValidationResult | ModelState |
十一、终极总结(框架级认知)
Attribute 是规则的“声明语言”
Reflection 是规则的“发现机制”
Validator 是规则的“执行引擎”
当你能手写出这一套时,说明你已经:
- 真正理解 Attribute 的价值
- 理解 Reflection 的正确用法
- 具备设计“声明式框架”的能力
208

被折叠的 条评论
为什么被折叠?



