关于.NET Attribute在数据校验中的应用教程

 更新时间:2020-06-14 00:02:02   作者:佚名   我要评论(0)

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

前言

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在数据校验的应用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

  • 详解.Net Core 权限验证与授权(AuthorizeFilter、ActionFilterAttribute)
  • ASP.NET MVC使用ActionFilterAttribute实现权限限制的方法(附demo源码下载)
  • .NET获取枚举DescriptionAttribute描述信息性能改进的多种方法
  • asp.net MVC利用ActionFilterAttribute过滤关键字的方法

相关文章

  • 关于.NET Attribute在数据校验中的应用教程

    关于.NET Attribute在数据校验中的应用教程

    前言 Attribute(特性)的概念不在此赘述了,相信有点.NET基础的开发人员都明白,用过Attribute的人也不在少数,毕竟很多框架都提供自定义的属性,类似于Newtonsoft
    2020-06-14
  • ASP.NET开源导入导出库Magicodes.IE完成Csv导入导出的方法

    ASP.NET开源导入导出库Magicodes.IE完成Csv导入导出的方法

    说明 本章主要说明如何使用Magicodes.IE.Csv进行Csv导入导出. 关于Magicodes.IE 导入导出通用库,通过导入导出DTO模型来控制导入和导出,支持Excel、Word、Pd
    2020-06-14
  • .net core 3.1在iis上发布的踩坑记录

    .net core 3.1在iis上发布的踩坑记录

    前言 写这篇文章的目的是希望像我一样喜欢.net 的人在发布 core到 iis上时少走点弯路 网上找了些资料,其实实际操作比较简单,就是有几个坑很恶心 踩坑记录 首先是你
    2020-06-14
  • .NET IoC模式依赖反转(DIP)、控制反转(Ioc)、依赖注入(DI)

    .NET IoC模式依赖反转(DIP)、控制反转(Ioc)、依赖注入(DI)

    依赖倒置原则(DIP) 依赖倒置(Dependency Inversion Principle,缩写DIP)是面向对象六大基本原则之一。他是指一种特定的的解耦形式,使得高层次的模块不依赖低层次的
    2020-06-14
  • asp.net mvc core管道及拦截器的理解

    asp.net mvc core管道及拦截器的理解

    今天来看一下asp.net core的执行管道。先看下官方说明: 从上图可以抛光,asp.net core的执行顺序是,当收到一个请求后,request请求会先经过已注册的中间件,然后
    2020-06-14
  • 聊一聊Asp.net过滤器Filter那一些事

    聊一聊Asp.net过滤器Filter那一些事

    最近在整理优化.net代码时,发现几个很不友好的处理现象:登录判断、权限认证、日志记录、异常处理等通用操作,在项目中的action中到处都是。在代码优化上,这一点是
    2020-06-14
  • xUnit 编写 ASP.NET Core 单元测试的方法

    xUnit 编写 ASP.NET Core 单元测试的方法

    还记得 .NET Framework 的 ASP.NET WebForm 吗?那个年代如果要在 Web 层做单元测试简直就是灾难啊。.NET Core 吸取教训,在设计上考虑到了可测试性,就连 ASP.NET
    2020-06-14
  • ASP.NET Core自定义中间件如何读取Request.Body与Response.Body的内容详解

    ASP.NET Core自定义中间件如何读取Request.Body与Response.Body的内容详解

    背景# 最近在徒手造轮子,编写一个ASP.NET Core的日志监控器,其中用到了自定义中间件读取Request.Body和Response.Body的内容,但是编写过程,并不像想象中的一帆
    2020-06-14
  • ASP.NET Core MVC如何实现运行时动态定义Controller类型

    ASP.NET Core MVC如何实现运行时动态定义Controller类型

    昨天有个朋友在微信上问我一个问题:他希望通过动态脚本的形式实现对ASP.NET Core MVC应用的扩展,比如在程序运行过程中上传一段C#脚本将其中定义的Controller类型注
    2020-06-14
  • 实例讲解PHP表单

    实例讲解PHP表单

    表单处理 GET vs. POST 1 GET 和 POST 都创建数组(例如,array( key => value, key2 => value2, key3 => value3, ...))。此数组包含键/值对,其中的键是表单控
    2020-06-10

最新评论