Application of.NET Attribute in Data Verification

The concept of Attribute is not redundant here, and I believe developers with a bit of a.NET foundation understand that there are not a few people who have used Attribute, since many frameworks provide custom attributes, such as JsonProperty, JsonIgnore, and so on, in Newtonsoft.JSON.

Custom Features

The.NET framework allows you to create custom attributes that store declarative information and can be retrieved at runtime.This information can be related to any target element based on design standards and application needs.

Creating and using custom features consists of four steps:

  • Declare custom attributes
  • Build custom features
  • Applying custom attributes on target program elements
  • Accessing features through reflection

Declare custom attributes

A new custom attribute must be derived from the System.Attribute class, for example:

public class FieldDescriptionAttribute : Attribute
{
    public string Description { get; private set; }

    public FieldDescriptionAttribute(string description)
    {
        Description = description;
    }
}
public class UserEntity
{
    [FieldDescription("User Name")]
    public string Name { get; set; }
}

How do I get the information we've labeled?Reflection acquisition is required at this point

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

The results are as follows:

From the execution results, we get the data we want, so what is the use of this feature in practice?

Attribute Features

In the actual development process, our system always provides a variety of external interfaces, among which parameter verification is an essential part.However, when there are no features, the code for the check is as follows:

  public class UserEntity
  {
      /// <summary>
      ///Name
      /// </summary>
      [FieldDescription("User Name")]
      public string Name { get; set; }

      /// <summary>
      ///Age
      /// </summary>
      public int Age { get; set; }

      /// <summary>
      ///Address
      /// </summary>
      public string Address { get; set; }
  }
      UserEntity user = new UserEntity();

      if (string.IsNullOrWhiteSpace(user.Name))
      {
          throw new Exception("Name cannot be empty");
      }
      if (user.Age <= 0)
      {
          throw new Exception("Illegal Age");
      }
      if (string.IsNullOrWhiteSpace(user.Address))
      {
          throw new Exception("Address cannot be empty");
      }

With more fields, the code looks cumbersome and intuitive.What can you do to optimize this tedious and disgusting code?
Verification after using the feature is written as follows:

First, define a basic check property to provide a basic check method

    public abstract class AbstractCustomAttribute : Attribute
    {
        /// <summary>
        ///Error message after verification
        /// </summary>
        public string ErrorMessage { get; set; }

        /// <summary>
        ///Data Checks
        /// </summary>
        /// <param name="value"></param>
        public abstract void Validate(object value);
    }

You can then define some common corresponding check attributes, such as Required Attribute, StringLengthAttribute

        /// <summary>
        ///Non-empty check
        /// </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) ? "Field cannot be empty" : ErrorMessage);
                }
            }
        }

        /// <summary>
        ///Custom Validation, Verify Character Length
        /// </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) ? $"Field length must be in{_minLength}and{_maxLength}Between" : ErrorMessage);
            }
        }

Add a ValidateExtensions for verification

public static class ValidateExtensions
  {
      /// <summary>
      ///Verification
      /// </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's field type needs to be differentiated for Object List arrays
              if (item.PropertyType.IsPrimitive || item.PropertyType.IsEnum || item.PropertyType.IsValueType || item.PropertyType == typeof(string))
              {
                  //Validate directly if primitive type, enumeration type, value type, or string
                  CheckProperty(entity, item);
              }
              else
              {
                  //If it is a reference type
                  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)
                      {
                          //Judging 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))//Here's the focus
          {
              //Here's the focus
              foreach (AbstractCustomAttribute attribute in property.GetCustomAttributes(typeof(AbstractCustomAttribute), true))
              {
                  if (attribute == null)
                  {
                      throw new Exception("AbstractCustomAttribute not instantiate");
                  }

                  attribute.Validate(property.GetValue(entity, null));
              }
          }
      }
  }

New Entity Class

  public class UserEntity
  {
      /// <summary>
      ///Name
      /// </summary>
      [Required]
      public string Name { get; set; }

      /// <summary>
      ///Age
      /// </summary>
      public int Age { get; set; }

      /// <summary>
      ///Address
      /// </summary>
      [Required]
      public string Address { get; set; }

      [StringLength(11, 11)]
      public string PhoneNum { get; set; }
  }

Call Method

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

The above checking logic is more complex, mainly considering that the objects contain complex objects. If they are all simple objects, you can not consider it, just check the fields for a single attribute.
The existing way is to throw an exception when the check fails. Here you can also customize the exception to represent the problem of the check, or return the customized entity of the check result to record which field the problem is currently in, for your own implementation.
If you have better suggestions and ideas, welcome to make progress together

These codes are shared by the original. If you think something is wrong, please leave a message to indicate that you are grateful

Author: hexuwsbg

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

Copyright: This license is granted under the "Accessory Origin Reproducible" Knowledge Sharing License Agreement

Tags: C# Attribute JSON

Posted on Tue, 12 May 2020 13:27:16 -0400 by cjbeck71081