.NET Attribute在數據校驗上的應用

Attribute(特性)的概念不在此贅述了,相信有點.NET基礎的開發人員都明白,用過Attribute的人也不在少數,畢竟不少框架都提供自定義的屬性,相似於Newtonsoft.JSON中JsonProperty、JsonIgnore等html

自定義特性

.NET 框架容許建立自定義特性,用於存儲聲明性的信息,且可在運行時被檢索。該信息根據設計標準和應用程序須要,可與任何目標元素相關。數組

建立並使用自定義特性包含四個步驟:框架

  • 聲明自定義特性
  • 構建自定義特性
  • 在目標程序元素上應用自定義特性
  • 經過反射訪問特性

聲明自定義特性

一個新的自定義特性必須派生自System.Attribute類,例如:ide

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

執行結果以下:
ui

從執行結果上看,咱們拿到了咱們想要的數據,那麼這個特性在實際使用過程當中,到底有什麼用途呢?this

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("地址不能爲空");
      }

字段多了以後這種代碼就看着很是繁瑣,而且看上去不直觀。對於這種繁瑣又噁心的代碼,有什麼方法能夠優化呢?
使用特性後的驗證寫法以下:code

首先定義一個基礎的校驗屬性,提供基礎的校驗方法htm

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

上面的校驗邏輯寫的比較複雜,主要是考慮到對象中包含複雜對象的狀況,若是都是簡單對象,能夠不用考慮,只需針對單個屬性作字段校驗
現有的方式是在校驗不經過的時候拋出異常,此處你們也能夠自定義異常來表示校驗的問題,也能夠返回自定義的校驗結果實體來記錄當前是哪一個字段出的問題,留待你們本身實現
若是您有更好的建議和想法歡迎提出,共同進步

以上代碼均爲原創分享,若你們認爲有不妥的地方,煩請留言指出,在下感激涕零

本文做者:hexuwsbg

出處:http://www.javashuo.com/article/p-zmqpxuuf-et.html

版權:本文采用「可附帶出處轉載」知識共享許可協議進行許可

相關文章
相關標籤/搜索