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