C# 后端数据验证的使用

数据验证

用户请求传入的任何参数必须做有效性验证。忽略参数校验可能导致:

  • 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 官方帮助文档:https://docs.fluentvalidation.net/en/latest/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值