8. 数据校验
📝 模块更新日志
8.1 关于数据校验
数据校验字面上的意思就是对使用者提交过来的数据进行合法性验证。在一套完善的应用系统中,数据有效性校验是必不可少的业务处理第一道关卡。
8.2 数据校验的好处
- 过滤不安全数据,提高系统的安全性
- 减少不必要的业务异常处理,提高系统的响应速度
- 大大提高系统稳定性
- 大数据并发时起着一定的缓冲作用
8.3 数据校验方式
- 传统方式,在业务代码之前手动验证
Mvc
特性方式,Mvc
内置的DataAnnotations
方式- 推荐方式,
Furion
框架内置的DataValidation
验证 - 其他方式,使用第三方验证库,如
FluentValidation
8.3.1 传统方式
在很多老项目中,我们经常看到这样的代码:
public bool Insert(Person person)
{
// 验证参数
if(string.IsNullOrEmty(person.Name))
{
throw new System.Exception("名字不能为空");
}
if(person.Age < 18)
{
throw new System.Exception("年龄不能小于 18 岁");
}
if(!person.Password.Equals(person.ConfirmPassword)
{
throw new System.Exception("两次密码不一致");
}
// 业务代码
_repository.Insert(person.Adapt<PersonEntity>());
// ...
}
从上面的代码看起来,似乎没有什么不妥,但是从一个程序可维护性来说,这是一个糟糕的代码,因为该业务代码中包含了太多与业务无关的数据验证。
试想一下,如果这个 Person
有 几十个参数都需要验证呢?可想而知,这是一个庞大的业务代码。
再者,如果其他地方也需要用到这个 Person
类验证呢?那代码好比老鼠啃过的面包屑一样,到处都是。
如此得知,这样的方式是极其不推荐的,不但污染了业务代码,也破坏了业务职责单一性原理,也让验证逻辑无法实现通用,后续维护难度大大升级。
8.3.2 Mvc
特性方式
在 ASP.NET Core
中,微软为我们提供了全新的 特性
验证方式,可通过对对象贴特性实现数据验证。这种方式有效的将数据校验和业务代码剥离开来,而且容易使用和拓展。
- 在模型中验证
using System.ComponentModel.DataAnnotations;
namespace Hoa.Application.Authorization.Dtos
{
public class SignInInput
{
[Required] // 必填验证
[MinLength(4)] // 最小长度验证
public string Account { get; set; }
[Required] // 必填验证
[MaxLength(32)] // 最大长度验证
public string Password { get; set; }
}
}
- 在参数中验证
public void CheckMethodParameterValid(
[Required] // 必填验证
[MinLength(4)] // 最小长度验证
string name,
int age,
[Required] // 必填验证
[RegularExpression("[a-zA-Z0-9_]{8,30}") // 正则表达式验证
string password,
[Required] // 必填验证
[RegularExpression("[a-zA-Z0-9_]{8,30}") // 正则表达式验证
string confirmPassword
)
{
// TODO
}
如果函数的参数大于或等于 3 个,建议抽离出模型类,也就是不建议上面的方式。
-
Mvc
内置特性[ValidateNever]
:指示熟悉或参数从验证中排除[CreditCard]
:信用卡格式验证[Compare]
:验证两个属性值是否匹配[EmailAddress]
:验证电子邮箱[Phone]
:验证电话号码[Range]
:验证指定范围[RegularExpression]
:验证属性值是否匹配正则表达式[Required]
:验证不为 null[StringLength]
:验证字符串长度[URL]
:验证是否有效的URL
格式[Remote]
:调用远程服务地址进行客户端验证
Mvc
内置特性想了解 Mvc
内置特性列表可查看官方文档 ASP.NET Core - 模型验证
- 自定义特性验证
public class ClassicMovieAttribute : ValidationAttribute
{
public ClassicMovieAttribute(int year)
{
Year = year;
}
public int Year { get; }
public string GetErrorMessage() =>
$"Classic movies must have a release year no later than {Year}.";
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var movie = (Movie)validationContext.ObjectInstance;
var releaseYear = ((DateTime)value).Year;
if (movie.Genre == Genre.Classic && releaseYear > Year)
{
return new ValidationResult(GetErrorMessage());
}
return ValidationResult.Success;
}
}
IValidatableObject
复杂验证
using System.Collections.Generic;
public class DtoModel : IValidatableObject
{
[Required]
[StringLength(100)]
public string Title { get; set; }
// 你的验证逻辑
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// 还可以解析服务
var service = validationContext.GetService(typeof(类型));
if (你的逻辑代码)
{
yield return new ValidationResult(
"错误消息"
,new[] { nameof(Title) } // 验证失败的属性
);
}
}
}
Mvc
特性方式极大的将业务逻辑和验证进行了剥离和解耦,而且还能实现自定义复杂验证。
但是 Mvc
特性验证方式有几个明显的缺点:
- 只能在
控制器
中的Action
(动作方法)中使用 - 无法在任意类、任意方法中使用
- 内置的验证类型非常有限,且不易拓展
- 不支持验证消息后期配置
所以,Furion
提供了新的验证引擎 DataValidation
,在完全兼容 Mvc
内置验证的同时提供了大量常见验证、复杂验证、自定义验证等能力。
8.4 DataValidation
验证 🤗
DataValidation
是 Furion
框架提供了全新的验证方式,完全兼容 Mvc
内置验证,并且赋予了超能。
8.4.1 DataValidation
优点
- 完全兼容
Mvc
内置验证引擎 - 内置常见验证类型及可自定义验证类型功能
- 提供全局对象拓展验证方式
- 支持验证消息后期配置,支持实时更新
- 支持在任何类,任何方法、任何位置实现手动验证、特性方式验证等
- 支持设置验证结果模型
8.5 DataValidation
使用
.AddDataValidation()
默认已经集成在 AddInject()
中了,无需再次注册。也就是 8.5.1
章节可不配置。
8.5.1 注册验证服务
using Microsoft.Extensions.DependencyInjection;
namespace Furion.Web.Core
{
[AppStartup(800)]
public sealed class FurWebCoreStartup : AppStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddDataValidation();
}
}
}
.AddDataValidation()
需在 services.AddControllers()
之后注册。
8.5.2 兼容 Mvc
特性验证
- TestDto
- FurionAppService
using System.ComponentModel.DataAnnotations;
namespace Furion.Application
{
public class TestDto
{
[Range(10, 20, ErrorMessage = "Id 只能在 10-20 区间取值")]
public int Id { get; set; }
[Required(ErrorMessage = "必填"), MinLength(3, ErrorMessage = "字符串长度不能少于3位")]
public string Name { get; set; }
}
}
using Furion.DynamicApiController;
namespace Furion.Application
{
public class FurionAppService : IDynamicApiController
{
/// <summary>
/// 值类型验证
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public int Get(int id)
{
return id;
}
/// <summary>
/// 对象类型验证
/// </summary>
/// <param name="testDto"></param>
/// <returns></returns>
public TestDto Post(TestDto testDto)
{
return testDto;
}
}
}
如下图所示:

8.5.3 兼容 Mvc
复杂验证
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Furion.Application
{
public class TestDto : IValidatableObject
{
[Range(10, 20, ErrorMessage = "Id 只能在 10-20 区间取值")]
public int Id { get; set; }
[Required(ErrorMessage = "必填"), MinLength(3, ErrorMessage = "字符串长度不能少于3位")]
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// 还可以解析服务
var service = validationContext.GetService(typeof(类型));
if (Name.StartsWith("Furion"))
{
yield return new ValidationResult(
"不能以 Furion 开头"
, new[] { nameof(Name) }
);
}
}
}
}
如下图所示:

8.6 手动验证
8.6.1 验证模型
using Furion.DataValidation;
using Furion.DynamicApiController;
namespace Furion.Application
{
public class FurionAppService : IDynamicApiController
{
[NonValidation] // 跳过全局验证
public DataValidationResult Post(TestDto testDto)
{
return testDto.TryValidate();
}
}
}
如下图所示:

支持 Mvc
内置的特性验证、属性验证及复杂的 IValidatableObject
验证。
8.6.2 TryValidate
和 Validate
Furion
提供了 TryValidate()
和 Validate()
两个验证拓展方法,唯一的区别就是后者验证失败将自动抛出异常消息。
8.6.3 ValidationTypes
常见验证
Furion
内置了很多常用类型的数据验证,包括:
Numeric
:数值类型PositiveNumber
:正数类型NegativeNumber
:负数类型Integer
:整数类型Money
:金钱类型Date
:日期类型Time
:时间类型IDCard
:身份证类型PostCode
:邮编类型PhoneNumber
:手机号类型Telephone
:固话类型PhoneOrTelNumber
:手机或固话类型EmailAddress
:邮件地址类型Url
:网址类型Color
:颜色值类型Chinese
:中文类型IPv4
:IPv4 地址类型IPv6
:IPv6 地址类型Age
:年龄类型ChineseName
:中文名类型EnglishName
:英文名类型Capital
:纯大写英文类型Lowercase
:纯小写英文类型Ascii
:Ascii 类型Md5
:Md5 字符串类型Zip
:压缩包格式类型Image
:图片格式类型Document
:文档格式类型MP3
:Mp3 格式类型Flash
:Flash 格式类型Video
:视频文件格式类型Html
:Html
标签格式类型IMEI
:手机机身码类型SocialCreditCode
:统一社会信用代码类型GUID_OR_UUID
:GUID
或UUID
类型Base64
:base64
格式类型
使用示例
- 单个类型验证
- 多个组合类型验证
// 验证中文
"我叫 MonK".TryValidate(ValidationTypes.Chinese); // => false
// 验证数值
2.TryValidate(ValidationTypes.Numeric); // => true
// 验证整数
true.TryValidate(ValidationTypes.Integer); // => false
// 验证邮箱
"monksoul@outlook.com".TryValidate(ValidationTypes.EmailAddress); // => true
// 验证负数
2.0m.TryValidate(ValidationTypes.NegativeNumber); // => false
// 自定义正则表达式验证
"Furion".TryValidate("/^Furion$"); // => true
// 验证数值类型且是整数
"20".TryValidate(ValidationTypes.Numeric, ValidationTypes.Integer); // => true
// 验证时日期或时间格式
"2020-05-20".TryValidate(ValidationPattern.AtLeastOne, ValidationTypes.Date, ValidationTypes.Time); // => true
"23:45:20".TryValidate(ValidationPattern.AtLeastOne, ValidationTypes.Date, ValidationTypes.Time); // => true
可通过设置 TryValidate([ValidationPattern], params object[] validationTypes)
方法的 ValidationPattern
参数配置验证逻辑,如:同时成立
或 只要一个成立
即可验证通过
8.6.4 [DataValidation]
特性
Furion
还提供了 [DataValidation]
特性方便在模型参数中使用 ValidationTypes
常见验证或自定义验证。
using Furion.DataValidation;
namespace Furion.Application
{
public class TestDto
{
[DataValidation(ValidationTypes.Integer)]
public int Id { get; set; }
[DataValidation(ValidationTypes.Numeric, ValidationTypes.Integer)]
public int Cost { get; set; }
[DataValidation(ValidationPattern.AtLeastOne, ValidationTypes.Chinese, ValidationTypes.Date)]
public string Name { get; set; }
// 可以和Mvc特性共存
[Required, DataValidation(ValidationTypes.Age)]
public int Age { get; set; }
[DataValidation(ValidationTypes.IDCard, ErrorMessage = "自定义身份证提示消息")]
public string IDCard { get; set; }
}
}
[DataValidation]
特性具备 ValidationAttribute
特性的所有配置以外还提供了以下配置:
ValidationTypes
:验证类型,Enum[]
类型,ValidationPattern
:验证逻辑,ValidationPattern
类型,可选AllOfThem(全部验证通过)
和AtleastOne(至少一个验证通过)
AllowNullValue
:是否允许空值,bool
类型,默认false
AllowEmptyStrings
:是否允许空字符串,bool
类型,默认false
在 Furion 4.8.8.42+
版本,[DataValidation]
的 ErrorMessage
支持 [Display]
和 [DisplayName]
特性指定格式化 {0}
信息,如:
[Display(Name = "手机号码")]
// [DisplayName("手机号码")]
[DataValidation(ValidationTypes.PhoneNumber, ErrorMessage = "不是一个正确的{0}")]
public string PhoneNumber { get; set; }
8.6.5 [ModelBinder]
特性
默认情况下,验证失败信息会根据属性名进行序列化,但是如果属性序列化自定义了 [JsonPropertyName]
特性,那么验证失败的消息就不匹配了,这时我们需要添加 [ModelBinder(Name = "序列化对应名字")]
进行纠正。如下图所示:
[JsonPropertyName("phone_number"), ModelBinder(Name = "phone_number")]
public string PhoneNumber { get; set; }
8.7 [NonValidation]
跳过验证
Furion
框架提供了对象模型跳过验证特性 [NonValidation]
,支持在 控制器
,动作方法
,类
中使用。
一旦贴了此特性,那么将不会执行验证操作。
[NonValidation]
只对对象类型有效,值类型无效。