数据验证
用户请求传入的任何参数必须做有效性验证。忽略参数校验可能导致:
* page size过大导致内存溢出
* 恶意order by导致数据库慢查询
* 任意重定向
* SQL注入
* 反序列化注入
* 正则输入源串拒绝服务ReDoS
复制代码
使用 System.ComponentModel.DataAnnotations 验证数据
使用 System.ComponentModel.DataAnnotations可以用标签来方便验证数据的有效性。添加`System.ComponentModel.DataAnnotations`的依赖。
复制代码
-
Key 表示唯一标识实体的一个或多个属性
/// <summary> /// Gets or sets the ID. /// </summary> [Key] public int ID { get; set; } 复制代码
-
Required 非空
/// <summary> /// 昵称 /// </summary> [Required(AllowEmptyStrings =false,ErrorMessage = "昵称不能为空")] public string Nickname { get; set; } 复制代码
-
StringLength 字符串长度
/// <summary> /// 个性签名 /// </summary> [StringLength(8, ErrorMessage = "个性签名长度必须在8-20位之间", MinimumLength = 20)] public string Sign { get; set; } 复制代码
-
RegularExpression 正则表达式验证
/// <summary> /// 电话号码 /// </summary> [RegularExpression("((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$)", ErrorMessage ="电话号码不符合格式")] public string Tel { get; set; } 复制代码
-
CreditCard 信用卡号验证(不建议使用)
/// <summary> /// 账号 /// </summary> [CreditCard(ErrorMessage = "不是有效的银行卡账号")] public string Account { get; set; } 复制代码
-
FileExtensions 文件扩展名验证(价值不大)
/// <summary> /// 头像 /// </summary> [FileExtensions(Extensions =".jpg",ErrorMessage ="头像只支持JPG格式")] public string AvatarUrl { get; set; } 复制代码
-
MaxLength MinLength 字符串或数组最大(最小)长度验证
/// <summary> /// 密码 /// </summary> [MaxLength(20,ErrorMessage = "密码长度必须在8-20位之间")] [MinLength(8, ErrorMessage = "密码长度必须在8-20位之间")] public string EncryPassword { get; set; } 复制代码
-
Phone 验证电话号码(不建议使用)
/// <summary> /// 电话号码 /// </summary> [Phone(ErrorMessage = "电话号码不符合格式")] public string Tel { get; set; } 复制代码
-
Url Url 地址验证(不建议使用)
/// <summary> /// 头像 /// </summary> [Url(ErrorMessage = "URL地址不正确")] public string AvatarUrl { get; set; } 复制代码
-
Range 验证数据值指定数值范围的约束
/// <summary> /// 创建时间 /// </summary> [Range(typeof(DateTime), "1/2/2017", "3/4/2018",ErrorMessage ="只接受2017-01-02到2018-03-04之间注册的用户")] public DateTime? CreateTime { get; set; } = DateTime.Now; 复制代码
-
CustomValidation 使用预定义方法验证(示例方法是 Accounts 类的 ValidationAccountType 方法)
/// <summary> /// 账号类型 /// </summary> [CustomValidation(typeof(Accounts), "ValidationAccountType", ErrorMessage = "账号类型错误!")] public int AccountType { get; set; } 复制代码
-
自定义验证属性
public class StrongPasswordAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value != null) { return new ValidationResult(string.Format("密码长度必须在8-20位之间且包含大小写字母和数字")); } return ValidationResult.Success; } } 复制代码
使用:
/// <summary> /// 密码 /// </summary> [StrongPassword] public string EncryPassword { get; set; } 复制代码
使用 System.ComponentModel.DataAnnotations 进行数据校验很简单也很灵活,但是也有一定局限性:一是只能在 asp.net 中使用,二是标签属性不支持表单验证,那么下面我介绍另外一个验证框架 FluentValidation
使用 FluentValidation 验证数据
FluentValidation 是一个使用 Linq 表达式,非常流畅的小型业务对象开源验证组件。遵循 Apache 2.0 开源协议。安装
NuGet Packages:Install-Package FluentValidation
复制代码
同样验证 Account
对象,FluentValidation 需要定义一个验证对象,我们取名 AccountValidator
它继承自 AbstractValidator<T>
,在此类型的构造函数对数据进行验证。验证的方式包括:
- 必填验证:
// 非空验证
RuleFor(account => account.ID).NotNull().WithMessage("ID不能为空");
// 非空及默认值验证,确保指定的属性不为空,空字符串或空白(或值类型的默认值,例如0代表int)
RuleFor(account => account.Nickname).NotEmpty().WithName("昵称");
复制代码
- 等于和不等于验证
// 一致验证和不一致验证
RuleFor(account => account.Sign).NotEqual(account => account.Nickname).WithName("个性签名");
RuleFor(account => account.Sign).Equal(account => account.Nickname).WithName("个性签名");
复制代码
- 字符串长度验证
// 长度验证
RuleFor(account => account.AccountCode).Length(16, 18).WithName("账号");
RuleFor(account => account.Sign).MaximumLength(500).MinimumLength(10).WithName("个性签名");
复制代码
- 数字、日期比较验证
RuleFor(account => account.CreateTime).LessThanOrEqualTo(new DateTime(2018, 12, 31))
.GreaterThanOrEqualTo(new DateTime(2018, 1, 1)).WithName("创建时间");
RuleFor(account => account.UpdateTime).LessThan(new DateTime(2018, 12, 31))
.GreaterThan(new DateTime(2018, 1, 1)).WithName("更新时间");
RuleFor(account => account.UpdateTime)
.ExclusiveBetween(new DateTime(2018, 1, 1), new DateTime(2018, 12, 31)).WithName("更新时间");
复制代码
- 特殊验证 Email 和信用卡
RuleFor(account => account.Email).EmailAddress().WithName("邮件地址");
RuleFor(account => account.AccountCode).CreditCard().WithName("账号");
复制代码
- 正则表达式验证
// 正则表达式验证
RuleFor(account => account.Tel)
.Matches(
@"((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$)")
.WithName("电话号码");
复制代码
- 自定义验证
// 验证ID是否已存在
RuleFor(account => account.ID).Custom(
(id, content) =>
{
if (Account.Exists(id))
{
content.AddFailure("ID已存在");
}
});
复制代码
// 带条件的自定义验证方法
RuleFor(account => account.UpdateTime).Must((account, updateTime) => updateTime > account.CreateTime)
.When(account => account.CreateTime.HasValue).WithMessage("{PropertyName}:{PropertyValue}必须晚于创建时间")
.WithName("更新时间");
复制代码
- 构建自己的验证器
首先我们创建一个数组条数对象ListCountValidator<T>
继承自属性验证基类PropertyValidator
public class ListCountValidator<T> : PropertyValidator
{
private readonly int _max;
public ListCountValidator(int max)
: base("{PropertyName} 必须小于 {MaxElements} 个元素.")
{
_max = max;
}
protected override bool IsValid(PropertyValidatorContext context)
{
// 验证数组对象长度是否已超过最大值
if (context.PropertyValue is IList<T> list && list.Count >= _max)
{
context.MessageFormatter.AppendArgument("最大元素", _max);
return false;
}
return true;
}
}
复制代码
然后我们就可以在验证中使用了
RuleFor(account => account.SubAccounts).SetValidator(new ListCountValidator<Account>(3)).WithMessage("子账户不能超过3个!");
复制代码
如果我们想要像其它验证器一样使用.
运算符调用,那么我们需要定义一个扩展。
public static class ListCountValidatorExtensions
{
public static IRuleBuilderOptions<T, IList<TElement>> ListCountMustLessThan<T, TElement>(
this IRuleBuilder<T, IList<TElement>> ruleBuilder, int num)
{
return ruleBuilder.SetValidator(new ListCountValidator<TElement>(num));
}
}
复制代码
这样我们就可以使用.
运算符调用了
RuleFor(account => account.SubAccounts).ListCountMustLessThan(3).WithMessage("子账户不能超过3个!");
复制代码
- 分组(表单)验证
FluentValidation 使用分组的方式进行表单验证,试想一个业务场景,在账号信息修改时必须记录修改时间和人员,那么我们可以通过以下代码实现。
RuleSet(
"Update", () =>
{
RuleFor(account => account.UpdateTime).NotEmpty().WithName("更新时间");
RuleFor(account => account.UpdateUser).NotEmpty().WithName("更新操作用户");
});
复制代码
- 验证调用
普通调用并输出错误列表
var validator = new AccountValidator();
var results = validator.Validate(account);
if (!results.IsValid)
{
var failures = results.Errors;
failures.ToList().ForEach(t => Console.WriteLine(t.ErrorMessage));
}
复制代码
抛出异常
var validator = new AccountValidator();
validator.ValidateAndThrow(account);
复制代码
调用分组(表单)验证
var validator = new AccountValidator();
var results = validator.Validate(account, ruleSet: "Update");
复制代码
注意:这样是不会调用除了Update
其它所有验证器,如果是要在原有验证基础上增加请使用以下方式:
var validator = new AccountValidator();
var results = validator.Validate(account, ruleSet: "default,Update");
复制代码
最后 FluentValidation 官方帮助文档:github.com/JeremySkinn…