手写 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 CoreModelState的雏形

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

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 的正确用法
  • 具备设计“声明式框架”的能力

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1098966.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在Miniconda-Python3.11中切换不同版本PyTorch进行对比实验

如何在 Miniconda-Python3.11 中切换不同版本 PyTorch 进行对比实验 在深度学习研究和模型开发中&#xff0c;一个看似微小的变量——PyTorch 版本&#xff0c;可能直接导致训练结果的巨大差异。你是否曾遇到过这样的情况&#xff1a;论文代码在最新版框架下无法复现&#xff…

轻量级Python环境崛起:Miniconda-Python3.11成为AI开发新宠

轻量级Python环境崛起&#xff1a;Miniconda-Python3.11成为AI开发新宠 在人工智能项目日益复杂的今天&#xff0c;一个看似不起眼的问题却频繁困扰开发者——“为什么我的代码在同事机器上跑不通&#xff1f;”更常见的情形是&#xff1a;刚升级完某个库&#xff0c;原本能运行…

JLink驱动安装通俗解释:写给嵌入式初学者的指南

JLink驱动安装通俗解释&#xff1a;写给嵌入式初学者的指南 为什么你连不上J-Link&#xff1f;从“插上没反应”说起 刚接触嵌入式开发的同学&#xff0c;常会遇到这样一个场景&#xff1a; 手里的STM32板子接好了线&#xff0c;J-Link调试器也插上了电脑USB口&#xff0c;打…

Jupyter Notebook实战入门:在Miniconda-Python3.11中运行你的第一个AI模型

Jupyter Notebook实战入门&#xff1a;在Miniconda-Python3.11中运行你的第一个AI模型 在人工智能项目开发中&#xff0c;最让人头疼的往往不是模型本身&#xff0c;而是“在我机器上能跑”这种环境不一致问题。你有没有遇到过这样的场景&#xff1a;好不容易复现一篇论文代码…

Miniconda-Python3.10镜像中安装XGBoost/LightGBM进行建模

在 Miniconda-Python3.10 环境中高效构建 XGBoost 与 LightGBM 模型 你有没有遇到过这样的场景&#xff1a;刚在本地跑通一个高性能的梯度提升模型&#xff0c;信心满满地交给同事复现&#xff0c;结果对方一运行就报错——“xgboost 导入失败”&#xff1f;再一看环境&#xf…

Miniconda-Python3.10镜像中使用scp/rsync传输大文件

Miniconda-Python3.10 镜像中使用 scp/rsync 传输大文件 在现代 AI 和数据科学项目中&#xff0c;动辄几十 GB 的模型权重、日志文件或训练数据集早已司空见惯。开发者常常需要在本地工作站与远程 GPU 服务器之间频繁交换这些“庞然大物”。如果每次修改一个检查点都要从头上传…

【视频】GStreamer+WebRTC(六):C++接口基础复习

1、最简示例 1.1 gst-launch-1.0命令 可以先使用 gst-launch-1.0 来测试,然后编码一步一步来实现: gst-launch-1.0 videotestsrc ! autovideosink 1.2 gst_parse_launch 实现 使用 gst_parse_launch 先解析GStreamer 字符串 “videotestsrc ! autovideosink”,直接生成 …

Miniconda-Python3.10镜像中配置SSH免密登录跳板机

Miniconda-Python3.10 镜像中配置 SSH 免密登录跳板机 在现代 AI 工程实践中&#xff0c;一个常见的痛点是&#xff1a;你已经写好了训练脚本、环境也配好了&#xff0c;却卡在“怎么安全又高效地连上远程 GPU 节点”这件事上。每次输入密码不仅繁琐&#xff0c;还让自动化成了…

Miniconda-Python3.10镜像中使用perf进行性能剖析

在 Miniconda-Python3.10 镜像中使用 perf 进行性能剖析 在人工智能和科学计算领域&#xff0c;Python 凭借其简洁语法与强大生态&#xff08;如 NumPy、Pandas、PyTorch&#xff09;已成为主流语言。但随着项目复杂度上升&#xff0c;尤其是模型训练或数据预处理任务变重时&a…

STM32CubeMX下载速度慢?Windows加速技巧分享

STM32CubeMX下载卡顿&#xff1f;一文搞定Windows网络加速实战 你是不是也经历过这样的场景&#xff1a;刚装好STM32CubeMX&#xff0c;兴致勃勃点开“Firmware Updater”&#xff0c;结果进度条纹丝不动&#xff0c;任务管理器里网络占用只有可怜的几百KB/s&#xff0c;甚至干…

Miniconda-Python3.10镜像中配置swap分区缓解内存压力

Miniconda-Python3.10镜像中配置swap分区缓解内存压力 在云服务器或边缘计算设备上跑一个 PyTorch 模型训练脚本&#xff0c;结果刚加载完数据集就“啪”一下进程被杀了——内核日志里清清楚楚写着 Out of memory: Kill process。这种情况对于使用轻量级开发环境的数据科学家来…

Keil5汉化常见问题:新手答疑与解决方案

Keil5汉化实战指南&#xff1a;新手避坑手册与深度排错方案 从“英文劝退”到全中文开发&#xff1a;为什么我们要汉化Keil&#xff1f; 在嵌入式开发的世界里&#xff0c; Keil MDK &#xff08;Microcontroller Development Kit&#xff09;几乎是每个ARM Cortex-M工程师…

Miniconda-Python3.10镜像中使用tar/zip压缩解压数据文件

Miniconda-Python3.10 环境中的数据压缩与解压实战 在 AI 项目开发中&#xff0c;一个常见的场景是&#xff1a;你刚刚从同事那里接手了一个新任务——训练一个图像分类模型。对方通过邮件发来一条下载链接&#xff0c;指向一个名为 dataset_v2.tar.gz 的文件。你把它上传到 Ju…

从零开始部署PyTorch GPU版本:基于Miniconda-Python3.11镜像实操指南

从零开始部署PyTorch GPU版本&#xff1a;基于Miniconda-Python3.11镜像实操指南 在深度学习项目开发中&#xff0c;最让人头疼的往往不是模型设计或训练调参&#xff0c;而是环境搭建——“为什么代码在我机器上跑得好好的&#xff0c;在服务器上却报错&#xff1f;”这种问题…

都是碳素管惹的祸:双通道电磁导航测量

简 介&#xff1a; 本文探讨了双通道电磁导航电路板中碳素管导电性对测量结果的影响。实验发现&#xff0c;使用导电的碳素管固定电感会产生严重干扰&#xff0c;改用绝缘胶水固定后测量数值趋于稳定。测试数据显示两路电磁信号增益存在30%差异&#xff0c;且输出波形不符合预期…

Miniconda-Python3.10镜像结合Prometheus监控GPU使用率

Miniconda-Python3.10镜像结合Prometheus监控GPU使用率 在深度学习项目日益复杂的今天&#xff0c;一个常见的痛点是&#xff1a;训练任务跑得慢&#xff0c;但查看系统状态时却发现 GPU 利用率长期徘徊在 10% 以下。更令人困扰的是&#xff0c;你无法判断这是模型本身的瓶颈、…

Jupyter Lab在Miniconda环境中的安装与安全访问配置

Jupyter Lab在Miniconda环境中的安装与安全访问配置 在高校实验室、AI初创公司或个人开发者的工作流中&#xff0c;一个常见但棘手的问题是&#xff1a;如何在一个共享的远程服务器上&#xff0c;既能高效开展深度学习实验&#xff0c;又能避免项目之间的依赖冲突&#xff0c;同…

基于交叉编译工具链的ARM平台驱动移植深度剖析

穿越架构鸿沟&#xff1a;如何用交叉编译打通ARM驱动开发的“任督二脉”你有没有遇到过这样的场景&#xff1f;写好了一段GPIO控制代码&#xff0c;兴冲冲地在PC上gcc编译一下&#xff0c;然后拷到树莓派上一运行——直接报错&#xff1a;“无法执行二进制文件&#xff1a;Exec…

Miniconda-Python3.10镜像支持法律文书智能审查系统

Miniconda-Python3.10镜像如何支撑法律文书智能审查系统 在法律科技&#xff08;LegalTech&#xff09;快速发展的今天&#xff0c;越来越多律所、法院和企业开始引入人工智能技术来提升文书处理效率。合同审核、条款比对、合规性检查等传统依赖人工的高耗时任务&#xff0c;正…

SSH远程开发配置指南:基于Miniconda-Python3.11的高效AI工作流

SSH远程开发配置指南&#xff1a;基于Miniconda-Python3.11的高效AI工作流 在高校实验室里&#xff0c;一个学生正对着自己轻薄本上“CUDA out of memory”的报错发愁&#xff1b;与此同时&#xff0c;百公里外的数据中心里&#xff0c;一块块A100显卡空转着等待任务。这并非个…