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

news/2024/5/19 22:08:46 标签: .net, c#, 服务器, Attribute, 特性, .netcore

上一章介绍了什么是特性以及.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;//实例化PrintLogAttribute
                    var 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);//反射获取属性值
                    //反射实例化StringLengthAttribute
                    StringLengthAttribute 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.niftyadmin.cn/n/5275177.html

相关文章

音视频直播核心技术介绍

直播流程 采集&#xff1a; 是视频直播开始的第一个环节&#xff0c;用户可以通过不同的终端采集视频&#xff0c;比如 iOS、Android、Mac、Windows 等。 前处理&#xff1a;主要就是美颜美型技术&#xff0c;以及还有加水印、模糊、去噪、滤镜等图像处理技术等等。 编码&#…

一文搞定Shiro

title: Shiro 快速入门 Shiro 快速入门 Shiro 是一个安全框架&#xff0c;具有认证、授权、加密、会话管理功能。 一、Shiro 简介 Shiro 特性 核心功能&#xff1a; Authentication - 认证。验证用户是不是拥有相应的身份。 Authorization - 授权。验证某个已认证的用户是否拥…

2023中国品牌节金谱奖荣誉发布 酷开科技获颁OTT行业科技创新奖

11月17日—19日&#xff0c;以“复苏与腾飞”为主题的2023第十七届中国品牌节&#xff0c;在杭州市云栖小镇国际会展中心成功举行。在18日晚间的荣耀盛典上&#xff0c;“TopBrand 2023中国品牌节金谱奖”荣誉发布&#xff0c;酷开科技斩获OTT行业科技创新奖。 酷开科技作为OTT…

使用Linux安装RStudio Server并实现远程访问

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、Cpolar杂谈 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 安装RStudio Server二. 本地访问三. Linux 安装cpolar四. 配置RStudio serv…

Leetcode—454.四数相加II【中等】

2023每日刷题&#xff08;六十四&#xff09; Leetcode—454.四数相加II 实现代码 class Solution { public:int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {unordered_map&l…

基于ssm缪斯乐器购物网站的设计与实现论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本缪斯乐器购物网站就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息…

leetcode每日一题打卡

leetcode每日一题 746.使用最小花费爬楼梯162.寻找峰值1901.寻找峰值Ⅱ 从2023年12月17日开始打卡~持续更新 746.使用最小花费爬楼梯 2023/12/17 代码 解法一 class Solution {public int minCostClimbingStairs(int[] cost) {int n cost.length;int[] dp new int[n1];dp[…

微服务组件Gateway的学习

Gateway Gateway基础概念Gateway简单使用Gateway路由工厂Gateway过滤器Gateway跨域配置 Gateway基础概念 API网关指系统的统一入口&#xff0c;它封装了应用程序的内部结构&#xff0c;为客户端提供统一服务&#xff0c;一些与业务本身功能无关的公共逻辑&#xff0c;可以在这…