zoukankan      html  css  js  c++  java
  • NET Attribute

    .NET Attribute在数据校验上的应用

    Attribute(特性)的概念不在此赘述了,相信有点.NET基础的开发人员都明白,用过Attribute的人也不在少数,毕竟很多框架都提供自定义的属性,类似于Newtonsoft.JSON中JsonProperty、JsonIgnore等

    自定义特性

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

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

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

    声明自定义特性

    一个新的自定义特性必须派生自System.Attribute类,例如:

    public class FieldDescriptionAttribute : Attribute
    {
        public string Description { get; private set; }
    
        public FieldDescriptionAttribute(string description)
        {
            Description = description;
        }
    }
    
    public class UserEntity
    {
        [FieldDescription("用户名称")]
        public string Name { get; set; }
    }
    

    该如何拿到我们标注的信息呢?这时候需要使用反射获取

          var type = typeof(UserEntity);
          var properties = type.GetProperties();
          foreach (var item in properties)
          {
              if(item.IsDefined(typeof(FieldDescriptionAttribute), true))
              {
                  var attribute = item.GetCustomAttribute(typeof(FieldDescriptionAttribute)) as FieldDescriptionAttribute;
                  Console.WriteLine(attribute.Description);
              }
          }
    

    执行结果如下:

    从执行结果上看,我们拿到了我们想要的数据,那么这个特性在实际使用过程中,到底有什么用途呢?

    Attribute特性妙用

    在实际开发过程中,我们的系统总会提供各种各样的对外接口,其中参数的校验是必不可少的一个环节。然而没有特性时,校验的代码是这样的:

      public class UserEntity
      {
          /// <summary>
          /// 姓名
          /// </summary>
          [FieldDescription("用户名称")]
          public string Name { get; set; }
    
          /// <summary>
          /// 年龄
          /// </summary>
          public int Age { get; set; }
    
          /// <summary>
          /// 地址
          /// </summary>
          public string Address { get; set; }
      }
    
          UserEntity user = new UserEntity();
    
          if (string.IsNullOrWhiteSpace(user.Name))
          {
              throw new Exception("姓名不能为空");
          }
          if (user.Age <= 0)
          {
              throw new Exception("年龄不合法");
          }
          if (string.IsNullOrWhiteSpace(user.Address))
          {
              throw new Exception("地址不能为空");
          }
    

    字段多了之后这种代码就看着非常繁琐,并且看上去不直观。对于这种繁琐又恶心的代码,有什么方法可以优化呢?
    使用特性后的验证写法如下:

    首先定义一个基础的校验属性,提供基础的校验方法

        public abstract class AbstractCustomAttribute : Attribute
        {
            /// <summary>
            /// 校验后的错误信息
            /// </summary>
            public string ErrorMessage { get; set; }
    
            /// <summary>
            /// 数据校验
            /// </summary>
            /// <param name="value"></param>
            public abstract void Validate(object value);
        }
    

    然后可以定义常用的一些对应的校验Attribute,例如RequiredAttribute、StringLengthAttribute

            /// <summary>
            /// 非空校验
            /// </summary>
            [AttributeUsage(AttributeTargets.Property)]
            public class RequiredAttribute : AbstractCustomAttribute
            {
                public override void Validate(object value)
                {
                    if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
                    {
                        throw new Exception(string.IsNullOrWhiteSpace(ErrorMessage) ? "字段不能为空" : ErrorMessage);
                    }
                }
            }
    
            /// <summary>
            /// 自定义验证,验证字符长度
            /// </summary>
            [AttributeUsage(AttributeTargets.Property)]
            public class StringLengthAttribute : AbstractCustomAttribute
            {
                private int _maxLength;
                private int _minLength;
    
                public StringLengthAttribute(int minLength, int maxLength)
                {
                    this._maxLength = maxLength;
                    this._minLength = minLength;
                }
    
                public override void Validate(object value)
                {
                    if (value != null && value.ToString().Length >= _minLength && value.ToString().Length <= _maxLength)
                    {
                        return;
                    }
    
                    throw new Exception(string.IsNullOrWhiteSpace(ErrorMessage) ? $"字段长度必须在{_minLength}与{_maxLength}之间" : ErrorMessage);
                }
            }
    

    添加一个用于校验的ValidateExtensions

    public static class ValidateExtensions
      {
          /// <summary>
          /// 校验
          /// </summary>
          /// <typeparam name="T"></typeparam>
          /// <returns></returns>
          public static void Validate<T>(this T entity) where T : class
          {
              Type type = entity.GetType();
    
              foreach (var item in type.GetProperties())
              {
                  //需要对Property的字段类型做区分处理针对Object List 数组需要做区分处理
                  if (item.PropertyType.IsPrimitive || item.PropertyType.IsEnum || item.PropertyType.IsValueType || item.PropertyType == typeof(string))
                  {
                      //如果是基元类型、枚举类型、值类型或者字符串 直接进行校验
                      CheckProperty(entity, item);
                  }
                  else
                  {
                      //如果是引用类型
                      var value = item.GetValue(entity, null);
                      CheckProperty(entity, item);
                      if (value != null)
                      {
                          if ((item.PropertyType.IsGenericType && Array.Exists(item.PropertyType.GetInterfaces(), t => t.GetGenericTypeDefinition() == typeof(IList<>))) || item.PropertyType.IsArray)
                          {
                              //判断IEnumerable
                              var enumeratorMI = item.PropertyType.GetMethod("GetEnumerator");
                              var enumerator = enumeratorMI.Invoke(value, null);
                              var moveNextMI = enumerator.GetType().GetMethod("MoveNext");
                              var currentMI = enumerator.GetType().GetProperty("Current");
                              int index = 0;
                              while (Convert.ToBoolean(moveNextMI.Invoke(enumerator, null)))
                              {
                                  var currentElement = currentMI.GetValue(enumerator, null);
                                  if (currentElement != null)
                                  {
                                      currentElement.Validate();
                                  }
                                  index++;
                              }
                          }
                          else
                          {
                              value.Validate();
                          }
                      }
                  }
              }
          }
    
          private static void CheckProperty(object entity, PropertyInfo property)
          {
              if (property.IsDefined(typeof(AbstractCustomAttribute), true))//此处是重点
              {
                  //此处是重点
                  foreach (AbstractCustomAttribute attribute in property.GetCustomAttributes(typeof(AbstractCustomAttribute), true))
                  {
                      if (attribute == null)
                      {
                          throw new Exception("AbstractCustomAttribute not instantiate");
                      }
    
                      attribute.Validate(property.GetValue(entity, null));
                  }
              }
          }
      }
    

    新的实体类

      public class UserEntity
      {
          /// <summary>
          /// 姓名
          /// </summary>
          [Required]
          public string Name { get; set; }
    
          /// <summary>
          /// 年龄
          /// </summary>
          public int Age { get; set; }
    
          /// <summary>
          /// 地址
          /// </summary>
          [Required]
          public string Address { get; set; }
    
          [StringLength(11, 11)]
          public string PhoneNum { get; set; }
      }
    

    调用方式

    UserEntity user = new UserEntity();
    user.Validate();
    

    上面的校验逻辑写的比较复杂,主要是考虑到对象中包含复杂对象的情况,如果都是简单对象,可以不用考虑,只需针对单个属性做字段校验
    现有的方式是在校验不通过的时候抛出异常,此处大家也可以自定义异常来表示校验的问题,也可以返回自定义的校验结果实体来记录当前是哪个字段出的问题,留待大家自己实现

  • 相关阅读:
    序列化二叉树
    把二叉树打印成多行
    按之字形顺序打印二叉树
    对称的二叉树
    二叉树的下一个节点
    java ee项目用gradle依赖打包
    spring cloud gateway 拦截request Body
    oauth2学习
    docker 应用
    gradle spring 配置解释
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/12912806.html
Copyright © 2011-2022 走看看