数据验证
用户请求传入的任何参数必须做有效性验证。忽略参数校验可能导致:
-
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.DataAnnotation
s 进行数据校验很简单也很灵活,但是也有一定局限性:一是只能在 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
官方帮助文档:https://docs.fluentvalidation.net/en/latest/index.html