第32篇 .Net特性Attribute的高级使用

今天给大家讲讲.net中特性的高级使用

1.什么是特性

特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。
用[]来标识,在.Net 框架提供了两种类型的特性:预定义特性和自定义特性。

可以用这个图来直观看看Attribute属性的结构
image

2.预定义特性

  • AttributeUsage
  • Conditional
  • obsolete

2.1 AttributeUsage
使用AttributeUsage特性,必须集成Attribute抽象类;

[AttributeUsage(AttributeTargets.Property)]//只能标记在属性上
public class MyCustomAttribute: Attribute
{

}

AttributeUsage 使用语法详细如下:

[AttributeUsage(AttributeTargets.Class |//特性只能运用于类上
AttributeTargets.Constructor |//特性只能运用于构造函数上
AttributeTargets.Field |//特性只能运用于字段上
AttributeTargets.Method |//特性只能运用于方法上
AttributeTargets.Property, //特性只能运用于属性上
AllowMultiple = true)]//true:可以为程序元素指定有多个实例

其中:

参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。

2.2 Conditional

这个预定义特性标记了一个条件方法,
它会引起方法调用的条件编译,取决于指定的值,比如 Debug 或 Trace。例如,当调试代码时显示变量的值。
规定该特性的语法如下:

public class MyTest
	{
		[Conditional("DEBUG")]
		public static void Message(string msg)
		{
			Console.WriteLine(msg);
		}
	}

 class Program
	{
		static void function1()
		{
			MyTest.Message("In Function 1.");
			function2();
		}
		static void function2()
		{
			MyTest.Message("In Function 2.");
		}

		static void Main(string[] args)
		{
			MyTest.Message("In Main function.");
			function1();
			Console.ReadLine();
	   }
}

当上面的代码被编译和执行时,它会产生下列结果:

image

2.3 Obsolete 过时

这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。

规定该特性的语法如下:
[Obsolete(message)]
[Obsolete(message, iserror)]

其中:

参数 message,是一个字符串,描述项目为什么过时的原因以及该替代使用什么。
参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。
下面的实例演示了该特性:

public class MyTest
	{
		[Obsolete("该方法已过期,你可使用xxx最新方法")]
		public static void Message(string msg)
		{
			Console.WriteLine(msg);
		}
	}

当编译程序时会出现如下效果,通常该特性用于在方法过期上、版本变更等等
image

public class MyTest
	{
		[Obsolete("该方法已经不可使用,请使用最新XXX方法",true)]
		public static void Message(string msg)
		{
			Console.WriteLine(msg);
		}
	}

当编译程序时会出现如下效果,可导致程序无法生成
image

3.自定义特性

Net 框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索。该信息根据设计标准和应用程序需要,可与任何目标元素相关。

创建并使用自定义特性包含四个步骤:

  • 声明自定义特性
  • 构建自定义特性
  • 在目标程序元素上应用自定义特性
  • 通过反射访问特性

最后一个步骤包含编写一个简单的程序来读取元数据以便查找各种符号。元数据是用于描述其他数据的数据和信息。该程序应使用反射来在运行时访问特性。

声明自动以特性

一个新的自定义特性应派生自 System.Attribute 类。例如:

/// <summary>
	/// 自定义日志打印
	/// </summary>
	[AttributeUsage(AttributeTargets.Method)]
	public class PrintLogAttribute: Attribute
	{
		private string _userName;
		private string _msg;
		public PrintLogAttribute(string userNaame, string msg)
		{
			this._userName = userNaame;
			this._msg = msg;
			Console.WriteLine($"{userNaame}于【{DateTime.Now.ToString("yyyy-MM-dd")}】{msg}");
		}
		public string GetMsg()
		{
			return $"{this._userName}于【{DateTime.Now.ToString("yyyy-MM-dd")}】{this._msg}";
		}
	}
	

public  class PrintLogTest
	{

		[PrintLog("张三","学习Attribute")]
		public   void Study()
		{
			Console.WriteLine("张三在学习....");
		}
		[PrintLog("张三", "SayHello")]
		public  string SayHello()
		{
			return "hello";
		}
	}


class Program
	{
		static void Main(string[] args)
		{
			PrintLogTest test=new PrintLogTest();
			test.Study();
			Type type = test.GetType();
			var methods = type.GetMethods();//获取所有公开方法
			foreach (MemberInfo item in methods)
			{
				if (item.IsDefined(typeof(PrintLogAttribute), true))//判断该方法是否被PrintLogAttribute标记
				{
					PrintLogAttribute attribute = item.GetCustomAttribute(typeof(PrintLogAttribute)) as PrintLogAttribute;//实例化PrintLogAttribute
					var msg = attribute.GetMsg();
					Console.WriteLine($"得到标记信息:{msg}");
				}
			}
			Console.ReadKey();
		}
	}

执行Main方法,执行如下:
image

4.Attribute特性的作用

使用面向抽象编程的思想进行优化,添加一个AbstractCustomAttribute抽象类,所有的校验类都继承AbstractCustomAttribute

/// <summary>
	/// 
	/// </summary>
	public abstract class AbstractCustomAttribute: Attribute//继承Attribute特性类
	{
		/// <summary>
		/// 定义校验抽象方法
		/// </summary>
		/// <param name="value">需要校验的值</param>
		/// <returns></returns>
		public abstract bool Validate(object value);
	}

RequiredAttribute、StringLengthAttribute自定义验证特性代码如下:

/// <summary>
	/// 自定义验证,验证不为空
	/// </summary>
	[AttributeUsage(AttributeTargets.Property)]
	public class RequiredAttribute : AbstractCustomAttribute
	{
		/// <summary>
		/// 重写Validate校验方法
		/// </summary>
		/// <param name="value">需要校验的参数</param>
		/// <returns></returns>
		public override bool Validate(object value)
		{
			return value != null && !string.IsNullOrWhiteSpace(value.ToString());
		}
	}


	/// <summary>
	/// 自定义验证,验证字符长度
	/// </summary>
	[AttributeUsage(AttributeTargets.Property)]
	public class StringLengthAttribute: AbstractCustomAttribute
	{
		private int _MaxLength;
		private int _MinLength;
		/// <summary>
		/// 
		/// </summary>
		/// <param name="MinLength">最小长度</param>
		/// <param name="MaxLength">最大长度</param>
		public StringLengthAttribute(int MinLength,int MaxLength)
		{
			this._MaxLength = MaxLength;
			this._MinLength = MinLength;
		}
		/// <summary>
		/// 重写Validate校验方法
		/// </summary>
		/// <param name="value">需要校验的参数</param>
		/// <returns></returns>
		public override bool Validate(object value)
		{
			return value != null && value.ToString().Length >= _MinLength && value.ToString().Length <= _MaxLength;
		}
	}

为了写升级代码,我添加了一个ValidateResultEntity实体类型,代码如下:

/// <summary>
	/// 校验结果实体类
	/// </summary>
	public class ValidateResultEntity
	{
		/// <summary>
		/// 是否校验成功
		/// </summary>
		public bool IsValidateSuccess { get; set; }

		/// <summary>
		/// 校验不通过的字段信息存储字段
		/// </summary>
		public List<FieidEntity> ValidateMessage { get; set; }
	}

	/// <summary>
	/// 字段信息
	/// </summary>
	public class FieidEntity
	{
		/// <summary>
		/// 字段名称
		/// </summary>
		public string FieidName { get; set; }
		/// <summary>
		/// 字段类型
		/// </summary>
		public string FieidType { get; set; }
		/// <summary>
		/// 验证错误时提示信息
		/// </summary>
		public string ErrorMessage { get; set; }
	}

终极版的RequiredAttribute、StringLengthAttribute自定义验证特性代码如下:

/// <summary>
	/// 自定义验证,验证不为空
	/// </summary>
	[AttributeUsage(AttributeTargets.Property)]
	public class RequiredAttribute : AbstractCustomAttribute
	{
		private string _ErrorMessage = "";
		public RequiredAttribute()
		{

		}
		public RequiredAttribute(string ErrorMessage)
		{
			this._ErrorMessage = ErrorMessage;
		}
		/// <summary>
		/// 重写Validate校验方法
		/// </summary>
		/// <param name="value">需要校验的参数</param>
		/// <returns></returns>
		public override FieidEntity Validate(object value)
		{
			if (value != null && !string.IsNullOrWhiteSpace(value.ToString()))
			{
				return null;
			}

			return new FieidEntity()
			{
				ErrorMessage = string.IsNullOrWhiteSpace(_ErrorMessage) ? "字段不能为空" : _ErrorMessage,
			};
		}
	}


	/// <summary>
	/// 自定义验证,验证字符长度
	/// </summary>
	[AttributeUsage(AttributeTargets.Property)]
	public class StringLengthAttribute: AbstractCustomAttribute
	{
		private int _MaxLength;
		private int _MinLength;
		private string _ErrorMessage;
		/// <summary>
		/// 
		/// </summary>
		/// <param name="MinLength">最小长度</param>
		/// <param name="MaxLength">最大长度</param>
		public StringLengthAttribute(int MinLength,int MaxLength,string ErrorMessage="")
		{
			this._MaxLength = MaxLength;
			this._MinLength = MinLength;
			this._ErrorMessage = ErrorMessage;
		}
		/// <summary>
		/// 重写Validate校验方法
		/// </summary>
		/// <param name="value">需要校验的参数</param>
		/// <returns></returns>
		public override FieidEntity Validate(object value)
		{
			if (value != null && value.ToString().Length >= _MinLength && value.ToString().Length <= _MaxLength)
			{
				return null;
			}
			return new FieidEntity()
			{
				ErrorMessage = string.IsNullOrWhiteSpace(_ErrorMessage) ? $"字段长度必须大于等于{_MinLength}并且小于等于{_MaxLength}" : _ErrorMessage,
			};
		}
	}

终极版的CustomValidateExtend类

public static class CustomValidateExtend
	{
		/// <summary>
		/// 校验
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		public static ValidateResultEntity Validate<T>(this T entity) where T:class
		{
			ValidateResultEntity validate=new ValidateResultEntity();
			validate.IsValidateSuccess= true;
			List<FieidEntity> fieidList = new List<FieidEntity>();
			Type type = entity.GetType();
			foreach (var item in type.GetProperties())
			{
				if (item.IsDefined(typeof(AbstractCustomAttribute), true))//此处是重点
				{
					//此处是重点
					foreach (AbstractCustomAttribute attribute in item.GetCustomAttributes(typeof(AbstractCustomAttribute), true))
					{
						if (attribute == null)
						{
							throw new Exception("AbstractCustomAttribute not instantiate");
						}

						var result = attribute.Validate(item.GetValue(entity));
						if (result != null)//校验不通过
						{
							result.FieidName = item.Name;//获取字段名称
							result.FieidType = item.PropertyType.Name;//获取字段类型
							fieidList.Add(result);//信息加入集合
							break;//此处为了防止字段被多个校验特性标注,只输出第一个验证不通过的校验信息
						}
					}
				}
			}
			if (fieidList.Count > 0)
			{
				validate.ValidateMessage = fieidList;
				validate.IsValidateSuccess = false;
			}
			return validate;
		}
	}

修改UserEntity实体类,添加自定义验证失败的错误信息

/// <summary>
	/// 
	/// </summary>
	public class UserEntity
	{
		/// <summary>
		/// 姓名
		/// </summary>
		[Required("姓名不能为空")]
		public string Name { get; set; }
		/// <summary>
		/// 年龄
		/// </summary>
		public int Age { get; set; }
		/// <summary>
		/// 家庭地址
		/// </summary>
		public string Address { get; set; }
		/// <summary>
		/// 性别
		/// </summary>
		public string Sex { get; set; }
		/// <summary>
		/// 手机号码
		/// </summary>
		[Required]
		[StringLength(11, 11,"手机号码必须等于11位")]
		public string PhoneNum { get; set; }
		/// <summary>
		/// 电子邮箱
		/// </summary>
		public string Email { get; set; }
	}

测试代码:

class Program
	{
		static void Main(string[] args)
		{

			UserEntity entity=new UserEntity();
			//entity.Name = "张三";
			//entity.PhoneNum = "1887065752";
			var validateResult = entity.Validate();//校验方法
			if (validateResult.IsValidateSuccess)
			{
				Console.WriteLine("验证通过");
			}
			else
			{
				Console.WriteLine("验证不通过");
				Console.WriteLine("================================================================");
				var data=JsonConvert.SerializeObject(validateResult.ValidateMessage);
				Console.WriteLine(data);//打印验证不通过的字段信息
			}

			Console.ReadKey();
		}
	}

image

最终我们做到了通过特性进行校验字段数据,不再写那种繁琐又臭又长的判断代码了。以上代码还可以继续优化,还可以使用泛型缓存提高其性能。

最后介绍一波微软的模型验证,
引用【System.ComponentModel.DataAnnotations】

里面有:

[Required]
[Range]
........
详情可查看【模型验证】

使用ActionFilterAttribute过滤器我们可以进行校验操作,核心代码如下:

/// <summary>
		/// 接口请求前操作,使用ActionFilterAttribute过滤器
		/// </summary>
		/// <param name="actionContext"></param>
		public override void OnActionExecuting(HttpActionContext actionContext)
		{
			if (!actionContext.ModelState.IsValid)
			{
				var data=new Dictionary<string,string>();
				if (actionContext.ModelState.Keys.Count > 0)
				{
					for (var i=0;i<actionContext.ModelState.Keys.Count;i++)
					{
						if (actionContext.ModelState.Values.ElementAt(i).Errors.Count > 0)
						{
							data.Add(actionContext.ModelState.Keys.ElementAt(i), actionContext.ModelState.Values.ElementAt(i).Errors.First().ErrorMessage);
						}
					}
				}
				actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, new
				{
					StatusCode = HttpStatusCode.BadRequest,
					Data = "",
					Message = "参数验证问题或必填参数未填写,请核对",
					Details = data 
				});
			}
		}
posted @ 2024-10-15 00:01  似梦亦非梦  阅读(211)  评论(0编辑  收藏  举报