.NET模型验证框架之FluentValidation的简单使用
FluentValidation 是一个基于 .NET 开发的验证框架,开源免费,而且优雅,支持链式操作,易于理解,功能完善,还是可与 MVC5、WebApi 和 ASP.NET Core深度集成,组件内提供十几种常用验证器,可扩展性好,支持自定义验证器,支持本地化多语言。
要使用验证框架,需要在项目中添加对 FluentValidation.dll 的引用,支持 netstandard2.0 库和 .NET4.5 平台,支持.NET Core 平台,最简单的方法是使用 NuGet 包管理器引用组件。
Install-Package FluentValidation
若要在 ASP.NET Core 中使用 FluentValidation 扩展,可引用以下包:
Install-Package FluentValidation.AspNetCore
若要在 ASP.NET MVC 5 或 WebApi 项目集成, 可以使用分别使用 FluentValidation.Mvc5 和 FluentValidation.WebApi 程序包。
Install-Package FluentValidation.Mvc5 Install-Package FluentValidation.WebApi
若要为特定对象定义一组验证规则, 您需要创建一个从 AbstractValidator<T> 继承的类, 其中泛型T参数是要验证的类的类型。假设您有一个客户类别:
public class Customer
{
public int Id { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
public decimal Discount { get; set; }
public string Address { get; set; }
}
接下来自定义继承于 AbstractValidator 泛型类的验证器,然后在构造函数中使用 LINQ 表达式编写 RuleFor 验证规则。
using FluentValidation;
using FluentValidation.Model;
public class CustomerValidator : AbstractValidator<Customer>
{
/// <summary>
/// 对客户类进行扩展并添加限制规则
/// </summary>
public CustomerValidator()
{
RuleFor(customer => customer.Surname).NotNull();
}
}
验证模型验证是否通过,需要CustomerValidator实例里的验证方法,参数是你的自定义模型,其中有两个属性,一个是错误还有一个是布尔类型是否验证通过。
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer(); //false
//Customer customer = new Customer() { Id=1, Address="1", Discount =250, Forename = "rerer", Surname = "zara"}; true
CustomerValidator validationRules = new CustomerValidator();
ValidationResult results = validationRules.Validate(customer);
System.Console.WriteLine(results.IsValid);
if (!results.IsValid)
{
foreach (var failure in results.Errors)
{
System.Console.WriteLine ("Property " + failure.PropertyName + " Error was: " + failure.ErrorMessage);
}
}
}
}
上面我们可以判定是否通过,它不会发生异常,如果想故意发生异常,类似下方的:
validationRules.ValidateAndThrow(customer);
在定义验证规则中,FluentValidation中还有链接规则写法,类似下方的:
public CustomerValidator()
{
RuleFor(customer => customer.Surname).NotNull().NotEmpty().NotEqual("zara")
.Length(1,500).MaximumLength(20).MinimumLength(100).EmailAddress()
.Matches("*&^&%(").GreaterThan("giao");
}
我们现在只能对单对象进行验证,那如何对集合进行验证呢,你可能会想到迭代…,已经提供好了(那绝逼还是迭代,手动滑稽),类似下方的:
public class Person
{
public List<string> addressLines { get; set; } = new List<string>();
}
public class PersonValidator :AbstractValidator<Person>
{
public PersonValidator()
{
RuleForEach(x => x.addressLines).NotNull();
}
}
如果面向与OOP,一定会有模型和模型之间的字段关系,那么我们可以这样去实现,类似下方的:
public class classly
{
public IList<student> students { get; set; }
}
public class student
{
public int classlyid { get; set; }
public string studentName { get; set; }
}
定义了一个 StudentValidator验证器件:
public class studentValidator : AbstractValidator<student>
{
public studentValidator()
{
RuleFor(x => x.studentName).NotNull();
RuleFor(x => x.classlyid).GreaterThan(0);
}
}
此验证程序可在 CustomerValidator 中通过 SetCollectionValidator 方法使用:
public class classlyValidator : AbstractValidator<classly>
{
public classlyValidator()
{
RuleFor(x => x.students).SetCollectionValidator(new studentValidator());
}
}
规则集允许您将验证规则组合在一起, 作为一个组一起执行, 同时忽略其他规则,假如:Person 类有3个属性分别是 Id、Surname 和 Forename,我们将 Id 单独验证, Surname 和 Forename 作为一组 Names 规则集进行验证。
public class PersonValidator : AbstractValidator<Person> {
public PersonValidator() {
RuleSet("Names", () => {
RuleFor(x => x.Surname).NotNull();
RuleFor(x => x.Forename).NotNull();
});
RuleFor(x => x.Id).NotEqual(0);
}
}
然后,我们可以使用验证器提供的扩展方法,针对指定的规则集执行验证,以下代码将不验证 Id 属性。
var validator = new PersonValidator();
var person = new Person();
var result = validator.Validate(person, ruleSet: "Names");
执行多个规则集验证,可以使用逗号分隔的字符串列表。
validator.Validate(person, ruleSet: "Names,MyRuleSet,SomeOtherRuleSet")
通过*号匹配所有规则, 可以强制执行所有规则, 不管它们是否在规则集中:
validator.Validate(person, ruleSet: "*")
如果您想灵活控制可重用的验证器, 则可以使用 Must 方法编写自定义规则,此方法允许您手动创建与验证错误关联的实例。
public classLyValidator()
{
RuleFor(x => x.students).Custom((list, context) =>
{
if (list.Count > 10)
{
context.AddFailure("The list must contain 10 items or fewer");
}
});
}
关于上下文添加错误,也可以定义多个信息,这需要引入 using FluentValidation.Results;
RuleFor(x => x.students).Custom((list, context) =>
{
if (list.Count > 10)
{
context.AddFailure(new ValidationFailure("SomeOtherProperty","This is first"));
context.AddFailure(new ValidationFailure("SomeOtherProperty", "This is second"));
}
});
在某些情况下, 针对某些属性的验证逻辑非常复杂, 我们希望将基于属性的自定义逻辑移动到单独的类中,可通过重写 PropertyValidator 类来完成。
public class SomeCountValidator<T> : PropertyValidator
{
private int _max;
public SomeCountValidator(int max)
: base("{PropertyName} must contain fewer than {MaxElements} items.")
{
_max = max;
}
protected override bool IsValid(PropertyValidatorContext context)
{
var list = context.PropertyValue as IList<T>;
if (list != null && list.Count >= _max)
{
context.MessageFormatter.AppendArgument("MaxElements", _max);
return false;
}
return true;
}
}
继承 PropertyValidator 时, 必须重写 IsValid 方法,此方法接受一个对象, 并返回一个布尔值, 指示验证是否成功。
在上下文中可以获取以下属性,其用处在以下代码中:
public class PropertyValidatorContext : IValidationContext
{
public PropertyValidatorContext(ValidationContext parentContext, PropertyRule rule, string propertyName);
public PropertyValidatorContext(ValidationContext parentContext, PropertyRule rule, string propertyName, object propertyValue);
public ValidationContext ParentContext { get; }
public PropertyRule Rule { get; }
public string PropertyName { get; } //对象名称
public string DisplayName { get; }
public object Instance { get; } //正在验证的对象
public MessageFormatter MessageFormatter { get; }
public object PropertyValue { get; }//正在验证的属性值
}
若要使用自定义的属性验证程序, 可以在定义验证规则时调用:
public class classLyValidator : AbstractValidator<classly>
{
public classLyValidator()
{
RuleFor(Person => Person.students).SetValidator(new SomeCountValidator<Person>(10));
}
}
WebApi + FluentValidation小实战:
在ASP.NET Web Api中请安装FluentValidation.AspNetCore版本
创建一个需要验证的Model
public class Product
{
public string name { get; set; }
public string des { get; set; }
public string place { get; set; }
}
配置FluentValidation,需要继承AbstractValidator类,并添加对应的验证规则
public class ProductValidator: AbstractValidator<Product> { public ProductValidator() { RuleFor(x => x.name) .NotEmpty() .WithMessage("name is not empty."); } }
现在我们应当注入验证服务
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// 添加验证器
services.AddSingleton<IValidator<Product>, ProductValidator>();
//mvc + validating
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddFluentValidation();
}
但现在还没有结束,因为Web Api中会自己给我们返回错误信息,也就找不到验证我们自定义的错误消息,所以我们要覆盖ModelState管理的默认行为(ApiBehaviorOptions)
public void ConfigureServices(IServiceCollection services)
{
// 添加验证器
services.AddSingleton<IValidator<Product>, ProductValidator>();
//mvc + validating
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddFluentValidation();// override modelstate
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = (context) =>
{
var errors = context.ModelState
.Values
.SelectMany(x => x.Errors
.Select(p => p.ErrorMessage))
.ToList();
var result = new
{
Code = "00009",
Message = "Validation errors",
Errors = errors
};
return new BadRequestObjectResult(result);
};
});
}
创建一个控制器,如果不符合规则就会曝出WithMessage的值。
[Route("api/[controller]")]
[ApiController]
public class DemoController : ControllerBase
{
[HttpPost]
public IActionResult Post(Customer customer)
{
return NoContent();
}
}