public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { RuleFor(x => x.Surname).NotEmpty(); RuleFor(x => x.Forename).NotEmpty().WithMessage("Please specify a first name"); RuleFor(x => x.Discount).NotEqual(0).When(x => x.HasDiscount); RuleFor(x => x.Address).Length(20, 250); RuleFor(x => x.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode"); } private bool BeAValidPostcode(string postcode) { // custom postcode validating logic goes here } }简单理解一下其中的几个关键要素:
7.默认常见内置规则,都有统一内置的验证不通过的消息;可通过WithMessage设置独立的验证不通过的消息;
用之前,我们通常需要引用的几个包:
「FluentValidation」 核心包,必须的
「FluentValidation.DependencyInjectionExtensions」 当你需要在依赖注入的场景下用的时候,这是必须的public void ConfigureServices(IServiceCollection services) { // 或者是 services.AddControllers(setup => services.AddMvc(setup => { //...mvc setup... }).AddFluentValidation(); }通过上面的代码启用 FluentValidation 后,MVC 将使用 FluentValidation 来验证 Controller 上 Action 中绑定的 Model 对象。
services.AddTransient<IValidator<Customer>, CustomerValidator>();可以想象,如果你有很多的 Model 类型和对应的验证规则设置,这样一个一个的注册,会心态崩溃,最终放弃的。
// 扫描并注册 Startup 类型所在程序集中的 Validator 验证器 services.AddValidatorsFromAssemblyContaining(typeof(Startup)); // 扫描并注册指定名称程序集中的所有 Validator 验证器 services.AddValidatorsFromAssembly(Assembly.Load("SomeAssembly"));嗯,很好,这样我就可以随意增加新的 Validator ,而不必担心忘记注册了。
AddFluentValidation(fv => { // 禁用 MVC 默认的 DataAnnotations 验证 fv.DisableDataAnnotationsValidation = true; });这样,我们的 ASP.NET Core 就会忽略默认的 DataAnnotations 验证。
services.AddMvc().AddFluentValidation(fv => { // 递归检查所有子属性的验证规则 fv.ImplicitlyValidateChildProperties = true; });虽然这样可以让你偷懒,但是我不建议这样做,因为验证器不只是 MVC 中需要的。我们在验证规则中应该明文设置子属性验证规则,这样也可针对不同的场景和业务要求让规则「显性」。
services.AddMvc().AddFluentValidation(fv => { fv.ImplicitlyValidateRootCollectionElements = true; });❝
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); } }这样在手动验证等场景下,可以通过代码指定仅验证规则集中的规则,而忽略其他规则:
var validator = new PersonValidator(); var person = new Person(); var result = validator.Validate(person, options => options.IncludeRuleSets("Names"));在 MediatR 的管线中对Request进行自动验证
public class RequestValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> { private readonly IEnumerable<IValidator<TRequest>> _validators; public RequestValidationBehavior(IEnumerable<IValidator<TRequest>> validators) { _validators = validators; } public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) { var failures = _validators .Select(v => v.Validate(request)) .SelectMany(result => result.Errors) .Where(f => f != null) .ToList(); if (failures.Count != 0) { throw new ValidationException(failures); } return next(); } }然后注册管线:
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestValidationBehavior<,>));
public class CustomExceptionHandlerMiddleware { private readonly RequestDelegate _next; private readonly ILogger<CustomExceptionHandlerMiddleware> _logger; public CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger<CustomExceptionHandlerMiddleware> logger) { _next = next; _logger = logger; } public async Task Invoke(HttpContext context) { try { await _next(context); } catch (Exception ex) { await HandleExceptionAsync(context, ex); } } private Task HandleExceptionAsync(HttpContext context, Exception exception) { var code = HttpStatusCode.InternalServerError; var result = string.Empty; switch (exception) { case ValidationException validationException: code = HttpStatusCode.BadRequest; result = JsonConvert.SerializeObject(new { code, message = validationException.Failures.First().Value.FirstOrDefault() ?? validationException.Message, details = validationException.Failures }); break; // case 其他需要统一处理的异常 } context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)code; if (string.IsNullOrEmpty(result)) { _logger.LogError("发生服务器端异常,{@exception}", exception); result = JsonConvert.SerializeObject(new { code, message = exception.Message }); } return context.Response.WriteAsync(result); } }结束语