.net5 - 创建Web.Api项目(四)DataAnnotations实现数据验证
命名空间
System.ComponentModel.DataAnnotations
全局模型验证,统一api响应
1、WebApi项目下新建文件夹【Custom】,新建文件夹【Filter】定义类FieldActionFilter
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System.Linq; namespace Test.WebApi.Custom.Filter { /// <summary> /// 验证参数 /// </summary> public class FieldActionFilter : IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { //throw new NotImplementedException(); } public void OnActionExecuting(ActionExecutingContext context) { ResponseDto result = new(); if (!context.ModelState.IsValid) { var errorMessage = context.ModelState.Values.SelectMany(v => v.Errors).FirstOrDefault(); result.Success = false; result.Message = errorMessage.ErrorMessage; context.Result = new JsonResult(result); } } } }
2、StartUp注册服务
services.AddMvc(options => { options.Filters.Add<FieldActionFilter>();//Dto参数验证 }); services.Configure<ApiBehaviorOptions>(options => { options.SuppressModelStateInvalidFilter = true; // 使用自定义模型验证【Api接口需要添加才能生效】 });
基础属性
属性名称 | 描述 |
---|---|
Required | 标识该属性为必需参数,不能为空 |
StringLength | 标识该字符串有长度限制,可以限制最小或最大长度 |
MaxLength | 不为null时,不能超过最大字符串长度 |
MixLength | 不为null时,不能少于最小字符串长度 |
Range | 标识该属性值范围,通常被用在数值型和日期型 |
Compare | 比较 - 与制定的字段值进行比较 具体见代码 [Compare(“MyOtherProperty”)]两个属性必须相同值,比如我们要求用户重复输入两次密码时有用 |
RegularExpression | 标识该属性将根据提供的正则表达式进行对比验证 |
Remote | 服务端验证 |
CustomValidation | 标识该属性将按照用户提供的自定义验证方法,进行数值验证 |
以下使用于表单:没有测试过,如需使用请自己测试 | |
DisplayName | 显示名 – 定义表单字段的提示名称,一般用于在PropertyGrid或者DataGridView里显示这个对象时,列名会自动显示DisplayName定义的值,而不是字段名 |
Bind | 绑定 – 列出在将请求参数绑定到模型的时候,包含和不包含的字段 |
ScaffoldColumn | 支架列 - 在编辑表单的时候,需要隐藏起来的的字符 [ScaffoldColumn(true|false)] |
DataType | 在前端显示的文本框类型 |
Editable | [Editable(false)] //放在主键上显示不可修改 |
Validations---这个是所有验证属性的基类
基础属性【Required、Range、StringLength、Compare】
using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Test.Application.Employee.Dto { public class AddEmployeeDto { /// <summary> /// 职员姓名不能为空 /// </summary> [Required(ErrorMessage = "职员姓名不能为空")] public string EmpName { get; set; } /// <summary> /// 密码 /// </summary> [Required(ErrorMessage = "密码不能为空")] [StringLength(12, MinimumLength = 6, ErrorMessage = "密码只能是6到12位")] public string Pwd { get; set; } /// <summary> /// 确认密码 /// </summary> [Compare("Pwd", ErrorMessage = "两次密码输入的不一致")] public string RPwd { get; set; } public string AAA { get; set; } /// <summary> /// 备注 /// </summary> [StringLength(200, ErrorMessage = "备注不能超过200字")] public string Remark { get; set; } /// <summary> /// 职员编号 /// </summary> [Required] //Swagger 字段后有一个*表示必填,实际上在传参的时候不填会有默认值0,这个没有起到实际意义上的验证效果 [Range(1, int.MaxValue, ErrorMessage = "职员编号不能为空")] //适用于自增长Id public int EmpId { get; set; } /// <summary> /// 备注2 /// </summary> [StringLength(200, MinimumLength = 1, ErrorMessage = "备注2只能是1至200字")] //可以为null,不能为"" public string Remark2 { get; set; } /// <summary> /// 年龄 /// </summary> [Range(18, 60, ErrorMessage = "年龄只能是18到60岁")] //可null类型,可以为null public int? Age { get; set; } /// <summary> /// 价格 /// </summary> [Range(9.99, 99.99, ErrorMessage = "价格只能是9.99至99.99")] public double Price { get; set; } /// <summary> /// 生日 /// </summary> [Range(typeof(DateTime), "2021-03-02", "2021-04-02", ErrorMessage = "日期只能是3月2号到4月2号")] //2021-04-02 00:00:01 不符合 public DateTime Birthday { get; set; } } }
基础属性【RegularExpression】
using System.ComponentModel.DataAnnotations; namespace Test.Application.Employee.Dto { /// <summary> /// 常工规则 /// 1、手机号码 /// 通用校验规则:^1[3456789]\d{9}$ /// 详细手机号校验规则:^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$ /// 验证手机号和固定电话:^((0\d{2,3}-\d{7,8})|(1[34578]\d{9}))$ /// 2、邮箱格式 /// [\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])? /// 3、用户名校验 /// 6-16位的包含大小写字母、数字、特殊符号- _ 的用户名:^[a-zA-Z0-9_-]{6,16}$ /// 6-20位字母和数字组合:^(?![0-9]*$)(?![a-zA-Z]*$)[a-zA-Z0-9]{6,20}$ /// 4、密码强度校验 /// 密码强度正则,最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符:^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$ /// 请输入6-20位英文字母、数字或者符号(除空格),且字母、数字和标点符号至少包含两种:^(?![\d]+$)(?![a-zA-Z]+$)(?![^\da-zA-Z]+$)([^\u4e00-\u9fa5\s]){6,20}$ /// 5、整数校验 /// 正整数正则:^\d+$ /// 负整数正则:^-\d+$ /// 整数正则:^-?\d+$ /// 6、身份证验证 /// (^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$) /// 7、合法url校验 /// ^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$ /// 8、验证输入内容是否包含英文数字及下划线 /// ^[_a-zA-Z0-9]+$ /// 9、验证是否两位小数 /// (^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$) /// 10、中文校验 /// ^[\u0391-\uFFE5A-Za-z]+$ /// 11、纯数字校验 /// ^\d+$|^\d+[.]?\d+$ /// 12、最多一位小数 /// ^[0-9]+([.]{1}[0-9]{1})?$ /// 13、ip地址校验 /// ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ /// 14、包含中文的校验 /// [\u4e00-\u9fa5] /// 15、内容只能由英文、数字、下划线组成 /// ^\w+$ /// 16、内容只能包含英文字母和数字 /// ^[a-z0-9]+$ /// 17、固定电话 /// ^((0\d{2,5}-)|0\d2,50\d2,5)?\d{7,8}(-\d{3,4})?$ /// </summary> public class ModifyEmployeeDto { /// <summary> /// 邮箱 /// </summary> [RegularExpression(@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4})$", ErrorMessage = "邮箱格式不正确")] public string Email { get; set; } /// <summary> /// 身份证 /// </summary> [RegularExpression(@"^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$|^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$", ErrorMessage = "错误")] public string IDCard { get; set; } } }
基础属性【Remote】【没有查到相关资料,有知道的小伙伴请在评论区留下代码】
自定义验证
在XXX.Service项目中新建文件夹【Base/Validation】新建类:EnumCheckAttribute
using System; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; namespace Test.Application.Base.Validation { /// <summary> /// 自定义验证 /// 验证是否是有效的枚举值 /// </summary> public class EnumCheckAttribute : ValidationAttribute { /// <summary> /// 验证的枚举类Type /// </summary> public Type TypeClass { get; set; } /// <summary> /// 枚举范围外的键,如有更好的方案请留言 /// </summary> public int OtherKey { get; set; } = int.MinValue; /// <summary> /// 枚举范围外的值 /// </summary> public string OtherValue { get; set; } public override bool IsValid(object value) { string enumValue = Enum.GetName(TypeClass, value); if (enumValue == null) { if (OtherKey == Convert.ToInt32(value)) { return true; } return false; } return true; } /// <summary> /// 提示中,添加枚举的键值对,如有更好的方案请留言 /// </summary> /// <param name="name"></param> /// <returns></returns> public override string FormatErrorMessage(string name) { return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, new object[] { EnumToJson() }); } /// <summary> /// 如有更好的方案请留言 /// </summary> /// <returns></returns> private string EnumToJson() { if (!TypeClass.IsEnum) throw new InvalidOperationException("enum expected"); var results = Enum.GetValues(TypeClass).Cast<object>().ToDictionary(enumValue => (int)enumValue, enumValue => enumValue.ToString()); //if (OtherKey != int.MinValue) //{ // results.Add(OtherKey, OtherValue); //} //return string.Format("{0}", JsonConvert.SerializeObject(results)).Replace("\"", ""); string result = "{"; if (OtherKey != int.MinValue) { result += OtherValue + ":" + OtherKey + ","; } foreach (var key in results.Keys) { result += results[key] + ":" + key + ","; } result = result.Remove(result.Length - 1); result += "}"; return result; } } }
使用
using Test.Application.Base.Validation; using Test.Model; namespace Test.Application.Employee.Dto { public class QueryEmployeeDto { /// <summary> /// 性别 /// </summary> [EnumCheck(TypeClass = typeof(Genders), OtherKey = -1, OtherValue = "全部", ErrorMessage = "性别无效,范围值为:{0}")] public int Gender { get; set; } } }