mvc 实体类验证的时候 如果有多个验证特性需要在属性上层叠很多个验证特性,显得属性特别臃肿并且也不够直观,极大地影响我使用它的兴趣,所以我想自定义一个验证特性,然后将所有需要验证的情形全部放在一个特性里,看上去更直观一点。
[DataContract] public partial class Sys_Menu : BaseModel { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DataMember] public int? MenuID { get; set; } [Validate(DisplayName = "菜单URL", MaxLength = 100)] [DataMember] public string URL { get; set; } [Validate(DisplayName = "菜单名", Required = true, MaxLength = 20)] [DataMember] public string Name { get; set; } [StringLength(100)] public string Description { get; set; } [DataMember] public int? ParentID { get; set; } public bool? IsActive { get; set; }
自定义的验证特性是不是看上去更清爽一点
实现
public class ValidateAttribute : ValidationAttribute, IClientValidatable { private const string EmailPattern = @"^w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*$"; private const string FixedPhonePattern = @"^(d{3,4}-)?d{6,8}$"; private const string MobilePhonePattern = @"^1d{10}$"; /// <summary> /// 是否必填项 /// </summary> public bool Required { get; set; } /// <summary> /// 数据格式 /// </summary> public FieldDataType DataType { get; set; } /// <summary> /// 展示名称 /// </summary> public string DisplayName { get; set; } /// <summary> /// 正则表达式 /// </summary> public string RegexPattern { get; set; } public int MaxLength { get; set; } public int MinLength { get; set; } /// <summary> /// 验证 /// </summary> /// <param name="value"></param> /// <param name="regx"></param> /// <returns></returns> private bool IsMatch(string value, string regx) { if (string.IsNullOrEmpty(value)) { return false; } bool isMatch = Regex.IsMatch(value, regx); return isMatch; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (string.IsNullOrEmpty(DisplayName)) { DisplayName = validationContext.MemberName; } //前后有空格 if (value is string && !value.Equals(value.ToString().Trim().ToString())) { IPropertyAccessor propertyAccessor = Caches.PropertyAccessorCache.Get(validationContext.ObjectType.GetProperty(validationContext.MemberName)); if (propertyAccessor != null) { propertyAccessor.SetValue(validationContext.ObjectInstance, value.ToString().Trim().ToString()); value = value.ToString().Trim().ToString(); } } ValidateResult result = Valid(value); if (!result.IsValid) { return new ValidationResult(result.ErrorMessage); } else { return ValidationResult.Success; } } /// <summary> /// 验证 如果不是必填项 只要不为空的才验证 待扩展 /// </summary> /// <param name="value"></param> /// <returns></returns> public ValidateResult Valid(object value) { if (this.Required) { if (value == null) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "不能为空" }; } if (value is string) { if (string.Empty.Equals(value.ToString())) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "不能为空" }; } } else if (value is ICollection) { if (((ICollection)value).Count == 0) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "条目不能为空" }; } } } if (value != null) { switch (DataType) { case FieldDataType.Email: if (!string.IsNullOrEmpty(value.ToString()) && !IsMatch(value.ToString(), EmailPattern)) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "不满足邮箱格式" }; } break; case FieldDataType.FixedPhone: if (!string.IsNullOrEmpty(value.ToString()) && !IsMatch(value.ToString(), FixedPhonePattern)) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "不满足固话格式" }; } break; case FieldDataType.MobilePhone: if (!string.IsNullOrEmpty(value.ToString()) && !IsMatch(value.ToString(), MobilePhonePattern)) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "不满足手机格式" }; } break; case FieldDataType.Phone: if (!string.IsNullOrEmpty(value.ToString()) && (!IsMatch(value.ToString(), MobilePhonePattern) || !IsMatch(value.ToString(), FixedPhonePattern))) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "不满足电话格式" }; } break; } if (!string.IsNullOrEmpty(RegexPattern)) { if (!string.IsNullOrEmpty(value.ToString()) && !IsMatch(value.ToString(), RegexPattern)) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "格式不正确" }; } } if (MaxLength != 0) { if (value is string) { if (!string.IsNullOrEmpty(value.ToString()) && value.ToString().Length > MaxLength) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "超出数据最大长度" }; } } else if (value is ICollection) { if (((ICollection)value).Count > MaxLength) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "超出最大条目" }; } } } if (MinLength != 0) { if (value is string) { if (!string.IsNullOrEmpty(value.ToString()) && value.ToString().Length < MinLength) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "超出数据最小长度" }; } } else if (value is ICollection) { if (((ICollection)value).Count < MinLength) { return new ValidateResult { IsValid = false, ErrorMessage = DisplayName + "超出最小条目" }; } } } } return new ValidateResult { IsValid = true, ErrorMessage = DisplayName + "验证通过" }; } public override string ToString() { StringBuilder sb = new StringBuilder(); if (this.Required) { sb.Append("Required"); } if (DataType != FieldDataType.None) { sb.Append(" DataType:" + DataType.ToString()); } if (!string.IsNullOrEmpty(RegexPattern)) { sb.Append(" RegexPattern:" + RegexPattern.ToString()); } if (MaxLength != 0) { sb.Append(" MaxLength:" + MaxLength.ToString()); } if (MinLength != 0) { sb.Append(" MinLength:" + MinLength.ToString()); } return sb.ToString(); } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { throw new NotImplementedException(); } }
可以根据自己的想法去验证
最后说说为什么要重写一个ToString(),这个是给HelpPage 接口说明文档使用的 我们将这个特性扩展进去,接口文档里开发人员就可以看到验证的说明
public class ModelDescriptionGenerator { // Modify this to support more data annotation attributes. private readonly IDictionary<Type, Func<object, string>> AnnotationTextGenerator = new Dictionary<Type, Func<object, string>> { { typeof(RequiredAttribute), a => "Required" }, { typeof(RangeAttribute), a => { RangeAttribute range = (RangeAttribute)a; return String.Format(CultureInfo.CurrentCulture, "Range: inclusive between {0} and {1}", range.Minimum, range.Maximum); } }, { typeof(MaxLengthAttribute), a => { MaxLengthAttribute maxLength = (MaxLengthAttribute)a; return String.Format(CultureInfo.CurrentCulture, "Max length: {0}", maxLength.Length); } }, { typeof(MinLengthAttribute), a => { MinLengthAttribute minLength = (MinLengthAttribute)a; return String.Format(CultureInfo.CurrentCulture, "Min length: {0}", minLength.Length); } }, { typeof(StringLengthAttribute), a => { StringLengthAttribute strLength = (StringLengthAttribute)a; return String.Format(CultureInfo.CurrentCulture, "String length: inclusive between {0} and {1}", strLength.MinimumLength, strLength.MaximumLength); } }, { typeof(DataTypeAttribute), a => { DataTypeAttribute dataType = (DataTypeAttribute)a; return String.Format(CultureInfo.CurrentCulture, "Data type: {0}", dataType.CustomDataType ?? dataType.DataType.ToString()); } }, { typeof(RegularExpressionAttribute), a => { RegularExpressionAttribute regularExpression = (RegularExpressionAttribute)a; return String.Format(CultureInfo.CurrentCulture, "Matching regular expression pattern: {0}", regularExpression.Pattern); } }, { typeof(ValidateAttribute), a => { ValidateAttribute validateExpression = (ValidateAttribute)a; return String.Format(CultureInfo.CurrentCulture, "Customer Validate: {0}", validateExpression.ToString()); } }, };
顺带说一下,假如我的实体层是独立的 也需要做一些配置
首先要自定义一个MultiXmlDocumentationProvider
public class MultiXmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider { /********* ** Properties *********/ /// <summary>The internal documentation providers for specific files.</summary> private readonly XmlDocumentationProvider[] Providers; /********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="paths">The physical paths to the XML documents.</param> public MultiXmlDocumentationProvider(params string[] paths) { this.Providers = paths.Select(p => new XmlDocumentationProvider(p)).ToArray(); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetDocumentation(MemberInfo subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetDocumentation(Type subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetDocumentation(HttpControllerDescriptor subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetDocumentation(HttpActionDescriptor subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetDocumentation(HttpParameterDescriptor subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetResponseDocumentation(HttpActionDescriptor subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /********* ** Private methods *********/ /// <summary>Get the first valid result from the collection of XML documentation providers.</summary> /// <param name="expr">The method to invoke.</param> private string GetFirstMatch(Func<XmlDocumentationProvider, string> expr) { return this.Providers .Select(expr) .FirstOrDefault(p => !String.IsNullOrWhiteSpace(p)); } }
public static class HelpPageConfig { [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Panasia.UserCenter.WebAPI.Areas.HelpPage.TextSample.#ctor(System.String)", Justification = "End users may choose to merge this string with existing localized resources.")] [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "bsonspec", Justification = "Part of a URI.")] public static void Register(HttpConfiguration config) { // Uncomment the following to use the documentation from XML documentation file. //config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml"))); config.SetDocumentationProvider(new MultiXmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/UserCenter.Model.xml"), HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));
右键项目属性设置XML文件生成路径
..XXX.UserCenter.WebAPIApp_DataXXX.UserCenter.Model.xml