前言

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();

上面的校验逻辑写的比较复杂,主要是考虑到对象中包含复杂对象的情况,如果都是简单对象,可以不用考虑,只需针对单个属性做字段校验

现有的方式是在校验不通过的时候抛出异常,此处大家也可以自定义异常来表示校验的问题,也可以返回自定义的校验结果实体来记录当前是哪个字段出的问题,留待大家自己实现

如果您有更好的建议和想法欢迎提出,共同进步

总结

到此这篇关于.net attribute在数据校验中的应用的文章就介绍到这了,更多相关.net attribute在数据校验的应用内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!