【51CTO.com原创稿件】数据验证是每个项目必须存在的,可以防止不符合系统规范的数据进入系统进而导致系统不稳定甚至崩溃。我们可以自己编写代码(包括前台和后台代码)进行验证,但是这样一方面代码量较大,另一方面有可能验证代码覆盖不完全。但是在 Entity Framework Core (以下简称 EF Core )中这些问题全可以解决。在 EF Core 中有两种验证模式,分别是内置模型验证和第三方扩展模型验证。下面我分别对这两种模式进行讲解,在讲解前我们先来创建必须的模型。
- public class User
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public int Age { get; set; }
- }
一、内置模型验证
在 EF Core 中并没有 Fluent API 模式对数据进行验证,因此我们只能通过 Data Annotations (数据注解)方式来进行数据验证,也就是添加特性的方法来验证数据。例如我们要验证 User 模型中的 Name 的长度,Name 长度不能大于 5 ,我们只需在 Name 属性上增加 StringLength 数据注解即可, StringLength 位于命名空间 System.ComponentModel.DataAnnotations 中,修改 User 模型代码如下:
- public class User
- {
- public int Id { get; set; }
- [StringLength(5)]
- public string Name { get; set; }
- public int Age { get; set; }
- }
上述代码通过 StringLength(5) 数据注解将 Name 属性的数据长度限定在 5 ,并且在数据提交时按照这个约定进行数据验证。下面我们就通过数据注解中的验证器来验证刚才添加的特性。首先我们要创建一个上下文的扩展方法:
- public static List<ValidationResult> ExecuteValidation(DbContext context)
- {
- List<ValidationResult> result = new List<ValidationResult>();
- var models = context.ChangeTracker.Entries()
- .Where(p =>
- (p.State == EntityState.Added) || (p.State == EntityState.Modified));
- foreach (var model in models)
- {
- var entity = model.Entity;
- var valProvider = new ValidationDbContextServiceProvider(context);
- var valContext = new ValidationContext(entity, valProvider, null);
- List<ValidationResult> error = new List<ValidationResult>();
- if(!Validator.TryValidateObject(entity,valContext,error,true))
- {
- result.AddRange(error);
- }
- return result.ToList();
- }
- }
在上述代码中我们通过 ChangeTracker 方法找出被追踪的实体,然后过滤出需要添加和更新的实体,对这些实体进行数据验证。最后我们通过 Validator 中的 TryValidateObject 方法验证实体数据并返回校验错误信息。在业务代码中我们调用前面定义的 ExecuteValidation 方法进行验证,如果验证通过就调用 EF Core 的 SaveChange() 方法,如果未通过就调用相应的处理代码,代码片段如下:
- if(context.ExecuteValidation().Any())
- {
- foreach(var error in context.ExecuteValidation())
- {
- //处理代码
- }
- }
- else
- {
- context.SaveChange();
- }
讲到这里估计会有很多小伙伴说每个业务代码中都要这么写太麻烦了,而且也产生了大量的重复代码。那么重复代码这个问题该怎么解决呢?这时一定有部分小伙伴想到了通过重写 SaveChanges 方法,将验证代码加入到这个方法中,这样就可以解决刚才的那个问题,达到一劳永逸的效果。具体代码如下:
- public override int SaveChanges(bool acceptAllChangesOnSucces)
- {
- var provider = ((IInfrastructure<IServiceProvider>)this).Instance;
- var items = new Dictionary<object, object>();
- var models = this.ChangeTracker.Entries()
- .Where(
- p => (p.State == EntityState.Added)||(p.State==EntityState.Modified));
- foreach (var model in models)
- {
- var entity = model.Entity;
- var context = new ValidationContext(entity, provider, items);
- List<ValidationResult> results = new List<ValidationResult>();
- if(!Validator.TryValidateObject(entity,context,results,true))
- {
- foreach (var result in results)
- {
- if(result!=ValidationResult.Success)
- {
- throw new ValidationException(result.ErrorMessage);
- }
- }
- }
- }
- return base.SaveChanges();
- }
通过上述代码就可以一处编写验证,多处使用了。具体的思路和前面所讲的一样,这里就不再进行讲解了。
二、第三方扩展模型验证
前面所讲的是通过数据注解的方式来进行数据验证的,但是如果是使用 Fluent API 的方式就没办法解决文章开头所说的问题,因为Fluent API 模式并没有提供对数据模型的验证。这时我们可以使用第三方扩展,在 EF Core 中常用的模型数据验证第三方扩展是 FluentValidation.AspNetCore 。在使用前我们需要在 NuGet 中下载此扩展。 FluentValidation.AspNetCore 安装完成后我们需要为模型创建验证器,验证器是一个继承自 AbstractValidator<T> 的类,验证规则使用 RuleFor 方法定义在验证器构造函数中。代码如下:
- public class ModelValidator:AbstractValidator<User>
- {
- public ModelValidator()
- {
- RuleFor(p => p.Name).NotEmpty().WithMessage("姓名不能为空");
- RuleFor(p => p.Name).MaximumLength(5).WithMessage("姓名长度在5字节");
- }
- }
上述代码进行了两个验证,一个是验证 Name 字段是否为空,另一个是验证 Name 字段的长度,其中我们通过 MaximumLength 规定了 Name 字段的最长长度为 5 字节。之后我们通过 WithMessage 方法返回我们自定义的错误信息。 我们定义完验证规则后下一步就是将我们定义的验证规则与应用程序连接起来,这里我们需要用到 AddFluentValidation 来注入,例如在 Asp.Net Core 程序中我们将注入程序写入 Startup 的 ConfigureServices 方法里。我们调用 AddFluentValidation 方法会将 FluentValidation 服务添加到 Asp.Net Core 中,然后使用 RegisterValidatorsFromAssembly 方法将自定义的验证代码注入到容器中,代码段如下:
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc()
- .AddFluentValidation(p=>
- p.RegisterValidatorsFromAssemblyContaining<Startup>());
- }
在需要验证数据的地方我们通过 ModelState 获取验证状态,验证通过就执行后续代码,不通过就执行处理代码。示例代码如下:
- if(ModelState.IsValid)
- {
- //后续代码
- }
- else
- {
- //验证不通过处理代码
- }
这里有一点需要注意,当传递的实体为 null 时,将返回错误信息,这是因为 AbstractValidator 中存在 EnsureInstanceNotNull 方法,这个方法在实例为 null 时会抛出异常,即使重写该方法也无法返回自定义的错误信息。如果需要验证实体集合就需要使用 RuleForEach 方法即可,对于自定义验证规则则可使用 SetValidator 方法。
三、总结
本篇文章讲解了 EF Core 数据验证的方法,虽然讲的是 EF Core 的方法,但是同样也适用于 EF6 ,这些内容是常用的,上述部分代码可以在大部分项目中通用。
作者简介:
朱钢,笔名喵叔,国内某技术博客认证专家,.NET高级开发工程师,7年一线开发经验,参与过电子政务系统和AI客服系统的开发,以及互联网招聘网站的架构设计,目前就职于一家初创公司,从事企业级安全监控系统的开发。
【51CTO原创稿件,合作站点转载请注明原文作者和出处为51CTO.com】