.Net Attribute 特性 自定义特性(二)

上一章介绍了什么是特性以及.net框架内的三种预定义特性,下面来看下如何自定义特性:

自定义特性

.Net 框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索。该信息根据设计标准和应用程序需要,可与任何目标元素相关。

创建并使用自定义特性包含四个步骤:

  • 声明自定义特性
  • 构建自定义特性
  • 在目标程序元素上应用自定义特性
  • 通过反射访问特性

最后一个步骤包含编写一个简单的程序来读取元数据以便查找各种符号。元数据是用于描述其他数据的数据和信息。该程序应使用反射来在运行时访问特性。

声明自定义特性
一个新的自定义特性应派生自 System.Attribute 类。例如:

/// <summary>/// 自定义日志打印/// </summary>[AttributeUsage(AttributeTargets.Method)]public class PrintLogAttribute: Attribute{private string _userName;private string _msg;public PrintLogAttribute(string userNaame, string msg){this._userName = userNaame;this._msg = msg;Console.WriteLine($"{userNaame}于【{DateTime.Now.ToString("yyyy-MM-dd")}】{msg}");}public string GetMsg(){return $"{this._userName}于【{DateTime.Now.ToString("yyyy-MM-dd")}】{this._msg}";}}public  class PrintLogTest{[PrintLog("张三","学习Attribute")]public   void Study(){Console.WriteLine("张三在学习....");}[PrintLog("张三", "SayHello")]public  string SayHello(){return "hello";}}class Program{static void Main(string[] args){PrintLogTest test = new PrintLogTest();test.SayHello();test.Study();Console.ReadKey();}}

执行Main方法,仅仅打印了特性里面的信息并没有被打印出来,这是为啥?想要获取标记的内容就需要用到反射,获取方法如下:

class Program{static void Main(string[] args){PrintLogTest test=new PrintLogTest();test.Study();Type type = test.GetType();var methods = type.GetMethods();//获取所有公开方法foreach (MemberInfo item in methods){if (item.IsDefined(typeof(PrintLogAttribute), true))//判断该方法是否被PrintLogAttribute标记{PrintLogAttribute attribute = item.GetCustomAttribute(typeof(PrintLogAttribute)) as PrintLogAttribute;//实例化PrintLogAttributevar msg = attribute.GetMsg();Console.WriteLine($"得到标记信息:{msg}");}}Console.ReadKey();}}

执行Main方法,执行如下:

从执行结果发现,我们拿到了我们想要信息。那么在实际过程中有哪些用途呢?接下来就进入文章主题。

Attribute特性实际妙用?
      在实际开发中,我们经常看到如MVC中标记在方法上的 [HttpGet] [HttpPost][HttpDelete][HttpPut] ,序列化时标记在类上的 [Serializable] ,使用EF标记属性的 [Key] ,使用特性的地方随处可见。那么特性到底有什么妙用?接下来通过一个实例来体现出Attribute特性的妙用。

众所周知,在开发中参数校验是必不可少的一个环节,什么参数不能为空、必须是手机格式、必须是邮箱格式,长度不能小于xx等等。这种校验在前端和后端都可以校验,对于一个后端开发者来说,有些校验放在前端有种把银行卡放到别人身上一样,总感觉不安全。所有后端进行校验总会让人很放心。

之前没有特性是后端校验代码是这样写的,如下:

   /// <summary>/// 建一个用户实体/// </summary>public class UserEntity{/// <summary>/// 姓名/// </summary>public string Name { get; set; }/// <summary>/// 年龄/// </summary>public int Age { get; set; }/// <summary>/// 家庭地址/// </summary>public string Address { get; set; }/// <summary>/// 性别/// </summary>public string Sex { get; set; }/// <summary>/// 手机号码/// </summary>public string PhoneNum { get; set; }/// <summary>/// 电子邮箱/// </summary>public string Email { get; set; }}

假如后台处理的时候传一个UserEntity过来,里面的参数都是必填,那么就需要进行校验了,普通的做法就是

 UserEntity entity=new UserEntity();if (entity != null){if (string.IsNullOrWhiteSpace(entity.Name)){throw new Exception("姓名不能为空");}if (entity.Age<=0||entity.Age>120){throw new Exception("年龄不合法");}if (string.IsNullOrWhiteSpace(entity.Address)){throw new Exception("家庭地址不能为空");}.....}

 字段多了后这种代码看着就繁琐,这还不包括手机格式验证、电子邮件验证等等,看着就不想写了,当然还有一种是在实体里面进行验证,验证实现就不一一列出,效果都是差不多。

看着以上即繁琐又恶心的代码,有什么方法可以解决呢?这下特性的用途就可以体现得淋漓尽致了。

使用特性后的验证写法如下:

先添加RequiredAttribute、StringLengthAttribute两个自定义验证特性

/// <summary>/// 自定义验证,验证不为空/// </summary>[AttributeUsage(AttributeTargets.Property)]public class RequiredAttribute:Attribute{}/// <summary>/// 自定义验证,验证字符长度/// </summary>[AttributeUsage(AttributeTargets.Property)]public class StringLengthAttribute: Attribute{public int _MaxLength;public int _MinLength;/// <summary>/// /// </summary>/// <param name="MinLength">最小长度</param>/// <param name="MaxLength">最大长度</param>public StringLengthAttribute(int MinLength,int MaxLength){this._MaxLength = MaxLength;this._MinLength = MinLength;}}

 添加一个用于校验的CustomValidateExtend类

 

public  class CustomValidateExtend{/// <summary>/// 校验/// </summary>/// <typeparam name="T"></typeparam>/// <returns></returns>public static bool Validate<T>(T entity) where T:class{Type type = entity.GetType();PropertyInfo[] properties = type.GetProperties();//通过反射获取所有属性foreach (var item in properties){if (item.IsDefined(typeof(RequiredAttribute), true))//判断该属性是否被RequiredAttribute特性进行标识{//字段被RequiredAttribute标识了var value=item.GetValue(entity);//反射获取属性值if (value == null || string.IsNullOrWhiteSpace(value.ToString()))//如果字段值为null 或""  "  ",则验证不通过{return false;}}if (item.IsDefined(typeof(StringLengthAttribute), true))//判断该属性是否被StringLengthAttribute特性进行标识{//字段被StringLengthAttribute标识了var value = item.GetValue(entity);//反射获取属性值//反射实例化StringLengthAttributeStringLengthAttribute attribute =item.GetCustomAttribute(typeof(StringLengthAttribute), true) as StringLengthAttribute;if (attribute == null){throw new Exception("StringLengthAttribute not instantiate");}if (value == null || value.ToString().Length < attribute._MinLength ||value.ToString().Length > attribute._MaxLength){return false;}}}return true;}}

在用户实体类中我们给Name、PhoneNum分别添加Required、StringLength特性标记

 

public class UserEntity{/// <summary>/// 姓名/// </summary>[Required]public string Name { get; set; }/// <summary>/// 年龄/// </summary>public int Age { get; set; }/// <summary>/// 家庭地址/// </summary>public string Address { get; set; }/// <summary>/// 性别/// </summary>public string Sex { get; set; }/// <summary>/// 手机号码/// </summary>[Required][StringLength(11, 11)]public string PhoneNum { get; set; }/// <summary>/// 电子邮箱/// </summary>public string Email { get; set; }}

 调用 CustomValidateExtend 中的 Validate 校验方法

class Program{static void Main(string[] args){UserEntity entity=new UserEntity();entity.Name = "张三";entity.PhoneNum = "18865245328";var validateResult =CustomValidateExtend.Validate(entity);if (validateResult){Console.WriteLine("验证通过");}else{Console.WriteLine("验证不通过");}Console.ReadKey();}}

 执行结果验证通过,把Name赋值为空或PhoneNum的长度小于或大于11,结果为验证不通过,目前为止,基于特性校验已经初步实现,对于追求完美的开发人员来说以下代码看着就不是很舒服。

代码再次升级,我们就使用面向抽象编程的思想进行优化,添加一个AbstractCustomAttribute抽象类,所有的校验类都继承AbstractCustomAttribute

/// <summary>/// /// </summary>public abstract class AbstractCustomAttribute: Attribute//继承Attribute特性类{/// <summary>/// 定义校验抽象方法/// </summary>/// <param name="value">需要校验的值</param>/// <returns></returns>public abstract bool Validate(object value);}


升级之后的RequiredAttribute、StringLengthAttribute自定义验证特性代码如下:

    /// <summary>/// 自定义验证,验证不为空/// </summary>[AttributeUsage(AttributeTargets.Property)]public class RequiredAttribute : AbstractCustomAttribute{public override bool Validate(object value){return value != null && !string.IsNullOrWhiteSpace(value.ToString());}}/// <summary>/// 自定义验证,验证字符长度/// </summary>[AttributeUsage(AttributeTargets.Property)]public class StringLengthAttribute : AbstractCustomAttribute{public int _MaxLength;public int _MinLength;/// <summary>/// /// </summary>/// <param name="MinLength">最小长度</param>/// <param name="MaxLength">最大长度</param>public StringLengthAttribute(int MinLength, int MaxLength){this._MaxLength = MaxLength;this._MinLength = MinLength;}public override bool Validate(object value){return value != null && value.ToString().Length >= _MinLength && value.ToString().Length <= _MaxLength;}}

升级后CustomValidateExtend类,重点

    public static class CustomValidateExtend{/// <summary>/// 校验/// </summary>/// <typeparam name="T"></typeparam>/// <returns></returns>public static bool Validate<T>(this T entity) where T : class{Type type = entity.GetType();foreach (var item in type.GetProperties()){if (item.IsDefined(typeof(AbstractCustomAttribute), true))//此处是重点{//此处是重点foreach (AbstractCustomAttribute attribute in item.GetCustomAttributes(typeof(AbstractCustomAttribute), true)){if (attribute == null){throw new Exception("StringLengthAttribute not instantiate");}if (!attribute.Validate(item.GetValue(entity))){return false;}                      }}}return true;}}

 执行校验方法

二次升级已完成,看看代码,瞬间心情舒畅。细心的朋友会发现,校验返回的都是true跟false,每次遇到校验不通过的字段后下面的都不再校验了,想要返回所有未校验通过的字段,并告诉调用者,一次性把所有字段都按照格式填好,这样才是我们想要的效果。

当然这样肯定是可以做到的,不要返回true跟false就行了,再次封装有一下就可以达到效果了。

为了写升级代码,我添加了一个ValidateResultEntity实体类型,代码如下:

/// <summary>/// 校验结果实体类/// </summary>public class ValidateResultEntity{/// <summary>/// 是否校验成功/// </summary>public bool IsValidateSuccess { get; set; }/// <summary>/// 校验不通过的字段信息存储字段/// </summary>public List<FieidEntity> ValidateMessage { get; set; }}/// <summary>/// 字段信息/// </summary>public class FieidEntity{/// <summary>/// 字段名称/// </summary>public string FieidName { get; set; }/// <summary>/// 字段类型/// </summary>public string FieidType { get; set; }/// <summary>/// 验证错误时提示信息/// </summary>public string ErrorMessage { get; set; }}

终极版的RequiredAttribute、StringLengthAttribute自定义验证特性代码如下:

    /// <summary>/// 自定义验证,验证不为空/// </summary>[AttributeUsage(AttributeTargets.Property)]public class RequiredAttribute : AbstractCustomAttribute{private string _ErrorMessage = "";public RequiredAttribute(){}public RequiredAttribute(string ErrorMessage){this._ErrorMessage = ErrorMessage;}/// <summary>/// 重写Validate校验方法/// </summary>/// <param name="value">需要校验的参数</param>/// <returns></returns>public override FieidEntity Validate(object value){if (value != null && !string.IsNullOrWhiteSpace(value.ToString())){return null;}return new FieidEntity(){ErrorMessage = string.IsNullOrWhiteSpace(_ErrorMessage) ? "字段不能为空" : _ErrorMessage,};}}/// <summary>/// 自定义验证,验证字符长度/// </summary>[AttributeUsage(AttributeTargets.Property)]public class StringLengthAttribute : AbstractCustomAttribute{private int _MaxLength;private int _MinLength;private string _ErrorMessage;/// <summary>/// /// </summary>/// <param name="MinLength">最小长度</param>/// <param name="MaxLength">最大长度</param>public StringLengthAttribute(int MinLength, int MaxLength, string ErrorMessage = ""){this._MaxLength = MaxLength;this._MinLength = MinLength;this._ErrorMessage = ErrorMessage;}/// <summary>/// 重写Validate校验方法/// </summary>/// <param name="value">需要校验的参数</param>/// <returns></returns>public override FieidEntity Validate(object value){if (value != null && value.ToString().Length >= _MinLength && value.ToString().Length <= _MaxLength){return null;}return new FieidEntity(){ErrorMessage = string.IsNullOrWhiteSpace(_ErrorMessage) ? $"字段长度必须大于等于{_MinLength}并且小于等于{_MaxLength}" : _ErrorMessage,};}}

 终极版的CustomValidateExtend类

    public static class CustomValidateExtend{/// <summary>/// 校验/// </summary>/// <typeparam name="T"></typeparam>/// <returns></returns>public static ValidateResultEntity Validate<T>(this T entity) where T : class{ValidateResultEntity validate = new ValidateResultEntity();validate.IsValidateSuccess = true;List<FieidEntity> fieidList = new List<FieidEntity>();Type type = entity.GetType();foreach (var item in type.GetProperties()){if (item.IsDefined(typeof(AbstractCustomAttribute), true))//此处是重点{//此处是重点foreach (AbstractCustomAttribute attribute in item.GetCustomAttributes(typeof(AbstractCustomAttribute), true)){if (attribute == null){throw new Exception("AbstractCustomAttribute not instantiate");}var result = attribute.Validate(item.GetValue(entity));if (result != null)//校验不通过{result.FieidName = item.Name;//获取字段名称result.FieidType = item.PropertyType.Name;//获取字段类型fieidList.Add(result);//信息加入集合break;//此处为了防止字段被多个校验特性标注,只输出第一个验证不通过的校验信息}}}}if (fieidList.Count > 0){validate.ValidateMessage = fieidList;validate.IsValidateSuccess = false;}return validate;}}

修改UserEntity实体类,添加自定义验证失败的错误信息

测试代码:

class Program{static void Main(string[] args){UserEntity entity=new UserEntity();//entity.Name = "张三";//entity.PhoneNum = "1887065752";var validateResult = entity.Validate();//校验方法if (validateResult.IsValidateSuccess){Console.WriteLine("验证通过");}else{Console.WriteLine("验证不通过");Console.WriteLine("================================================================");var data=JsonConvert.SerializeObject(validateResult.ValidateMessage);Console.WriteLine(data);//打印验证不通过的字段信息}Console.ReadKey();}}

测试结果如下:

最终我们做到了通过特性进行校验字段数据,不再写那种繁琐又臭又长的判断代码了。以上代码还可以继续优化,还可以使用泛型缓存提高其性能。

参考文献https://www.cnblogs.com/jiangxifanzhouyudu/p/11107734.html
 

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

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

相关文章

JS中的异常处理:

throw: 抛出异常时我们哪个关键字&#xff1f;它会终止程序&#xff1f; throw关键字 会终止程序 抛出异常经常和谁配合使用&#xff1f; Error对象配合throw使用 代码演示&#xff1a; function fn(x,y){if(!x || !y){// console.log(11);// throw 用户没有参数传递进来;th…

独立站如何借助内容营销实现品牌提升与用户增长?

在当今竞争激烈的数字市场中&#xff0c;独立站想要实现品牌提升与用户增长&#xff0c;内容营销是一条不可或缺的路径。通过有针对性、高质量的内容&#xff0c;独立站可以吸引目标受众&#xff0c;建立品牌认知&#xff0c;并最终促进用户增长。本文Nox聚星将和大家探讨内容营…

sql_lab靶场搭建以及存在的一些问题

sql_lab靶场搭建问题 首先检查小皮版本 把小皮改到5.3.29版本如果没有可以直接点击更多版本进行选择安装 当版本不对时则会暴出这种错误 SETTING UP THE DATABASE SCHEMA AND POPULATING DATA IN TABLES: Fatal error: Uncaught Error: Call to undefined function mysql_co…

《Linux C编程实战》笔记:进程操作之退出,执行,等待

进程退出 进程退出表示进程即将运行结束。在Linux中退出分为正常退出和异常退出。 正常退出&#xff1a; 在main函数中执行return调用exit函数调用_exit函数 异常退出&#xff1a; 调用abort函数收到某个信号&#xff0c;这个信号是程序终止 退出方式比较 exit和return的…

软件代码签名的作用

随着“互联网”时代的到来&#xff0c;人们的生活变得更加便利&#xff0c;但电信诈骗、信息泄露、恶意软件等也随之而来&#xff0c;软件安全问题也越来越受到关注。为了确保软件的身份和完整性&#xff0c;越来越多的软件开发者和企业选择使用软件代码签名证书。为什么我们会…

多线程并发实现生产者/消费者

前言 无需引入第三方消息队列组件&#xff0c;我们如何利用内置C#语法高效实现生产者/消费者对数据进行处理呢&#xff1f;在.NET Core共享框架&#xff08;Share Framework&#xff09;引入了通道&#xff08;Channel&#xff09;&#xff0c;也就是说无需额外通过NuGet包安装…

Graylog配置GraylogSidecar-传输日志文件

1.GraylogSidecar概述 Graylog Sidecar 是 Graylog 日志管理系统的一个组件&#xff0c;用于配置和管理通过 Filebeat、Winlogbeat、NXLog 或其他日志收集器发送的日志流。它的作用是管理和配置这些日志收集器&#xff0c;确保它们正确地发送日志数据到 Graylog 服务器。 我用的…

centos开机自启动实战小案例

1.编写一个我们需要做事的脚本 #!/bin/bash # 打印 "Hello" echo "Hello,Mr.Phor" # 为了更好的能看到效果 我们把这段文本放置到一个文件中 如果重启能够看到 /a.txt文件 我们实验成功 echo "hahahahahahahaha" > /a.txt #每次开机 执行…

python——异常机制及常见异常汇总

异常机制本质 异常指程序运行过程中出现的非正常现象&#xff0c;例如用户输入错误、除数为零、需 要处理的文件不存在、数组下标越界等。 所谓异常处理&#xff0c;就是指程序在出现问题时依然可以正确的执行剩余的程序&#xff0c;而 不会因为异常而终止程序执行。 python…

网络机顶盒什么牌子好?2024业内最新网络电视机顶盒排名

网络机顶盒是我们平时不可缺少的部分&#xff0c;但依然有很多消费者在挑选网络机顶盒时踩雷&#xff0c;不懂网络机顶盒什么牌子好&#xff0c;业内发布了最新的好评网络机顶盒排名&#xff0c;想知道哪些网络机顶盒值得买可以了解以下这五款口碑最好的网络机顶盒。 榜一&…

基于html5的演唱会购票系统的设计与实现论文

基于html5的演唱会购票系统的设计与实现 摘要 随着信息互联网购物的飞速发展&#xff0c;一般企业都去创建属于自己的电商平台以及购物管理系统。本文介绍了基于html5的演唱会购票系统的设计与实现的开发全过程。通过分析企业对于基于html5的演唱会购票系统的设计与实现的需求…

从数据系统的角度思考研发效能(DevOps)的提升丨IDCF

作者&#xff1a;李宏喜 IDCF研发效能&#xff08;DevOps&#xff09;工程师&#xff08;中级&#xff09;认证学员 在软件研发效能&#xff08;DevOps&#xff09;的学习过程中&#xff0c;我认识到诸位老师从软件全生命周期的不同角度&#xff0c;讲述着研发效能的提升。 敏…