天天看點

.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特性妙用

在實際開發過程中,我們的系統總會提供各種各樣的對外接口,其中參數的校驗是必不可少的一個環節。然而沒有特性時,校驗的代碼是這樣的:

{

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

新的實體類

/// <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

出處:

https://www.cnblogs.com/hexu0512/p/12879671.html