本文将深入探讨如何使用 FluentValidation 在 .NET 应用程序中实现验证。传统上,.NET 应用程序过去常常使用数据批注进行验证,但这种方法曾经存在一些限制,例如模型或 dto 类看起来臃肿、不可扩展、无法控制消息和行为以及测试不灵活。这就是像 FluentValidation 这样的库帮助解决批次问题的地方。
FluentValidation 是一个库,它提供了一个流畅的接口,用于定义 .NET 中对象的验证规则。它允许您使用清晰且富有表现力的语法来定义复杂的验证逻辑。
目的
设计模式
FluentValidation 遵循 Fluent Interface 设计模式,提供流畅且富有表现力的 API 来定义验证规则。它提倡一种清晰易读的语法来定义复杂的验证逻辑。
何时使用
现在,让我们设置一个示例 API,并演示如何在 .NET 中使用 FluentValidation。在本例中,让我们假设我们有一个 Order API,它将创建客户并创建订单,在这种情况下,让我们使用 FluentValidation 构建一些验证器。在此之前,我们需要安装几个 NuGet 包,如下所示。
在业务逻辑项目中,所有验证器和逻辑都保留添加以下包:
dotnet add package FluentValidation
在 API 项目中添加以下包:
dotnet add package FluentValidation.AspNetCore
现在,让我们构建一些示例验证器来验证客户、地址和订单。
FluentValidation 有几个内置的验证器,这让我们无需编写代码来执行最常见的验证。如下图所示,我们甚至可以像 NotEmpty 和 MaximumLength 这样的链式验证。
using BusinessLogic.Models;
using FluentValidation;
namespace BusinessLogic.Validators
{
public class OrderRequestValidator : AbstractValidator<OrderRequest>
{
public OrderRequestValidator()
{
RuleFor(or=>or.ProductName).NotEmpty().MaximumLength(255).WithMessage("Product Name Required for placing order");
RuleFor(or => or.Quantity).GreaterThan(0).LessThan(50);
RuleFor(or => or.PromoCode).NotEmpty().MaximumLength(10);
}
}
}
我们可以将验证器嵌套在另一个验证器中,如下图所示,我们在客户验证器中调用订单和地址验证器。
using BusinessLogic.Models;
using FluentValidation;
namespace BusinessLogic.Validators
{
public class CreateCustomerRequestValidator : AbstractValidator<CreateCustomerRequest>
{
public CreateCustomerRequestValidator()
{
RuleFor(cus => cus.FirstName).NotEmpty().MaximumLength(100);
RuleFor(cus => cus.LastName).NotEmpty().MaximumLength(100);
RuleFor(cus => cus.Email).NotEmpty().EmailAddress().WithMessage("Email not in expected format");
RuleFor(cus => cus.Age).GreaterThan(20);
RuleFor(cus => cus.PhysicalAddress).NotNull().SetValidator(new AddressRequestValidator());
RuleFor(cus => cus.MailingAddress).NotNull().SetValidator(new AddressRequestValidator());
RuleFor(cus => cus.Order).NotNull().SetValidator(new OrderRequestValidator());
}
}
}
假设我们想要构建可扩展的自定义验证器,在我们的例子中,验证邮政编码是所有数字。没有像验证电子邮件等开箱即用的验证器,因此我们必须实现自己的自定义验证器扩展 IRuleBuilder,如下所示。
using FluentValidation;
namespace BusinessLogic.Validators
{
public static class FluentValidationExtensions
{
public static IRuleBuilderOptions<T, string> ZipCode<T>(this IRuleBuilder<T, string> ruleBuilder)
{
//"^[0-9]{5}(?:-[0-9]{4})?$"
return ruleBuilder
.NotEmpty()
.Matches(@"\d{5}$");
}
}
}
为了在我们的 Web API 项目中使用所有这些验证器,我们需要注册它们并使用它们,如下所示。
using BusinessLogic.Validators;
using FluentValidation;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Add each and every validator one after another
//builder.Services.AddScoped<IValidator<CreateCustomerRequestValidator>, CreateCustomerRequestValidator>();
//Register all the validators in one go
builder.Services.AddValidatorsFromAssemblyContaining<CreateCustomerRequestValidator>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
using BusinessLogic.Models;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;
namespace OrderApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class OrderController : ControllerBase
{
private IValidator<CreateCustomerRequest> _validator;
public OrderController(IValidator<CreateCustomerRequest> validator)
{
_validator = validator;
}
[HttpPost]
public async Task<ActionResult> PostCustomer([FromBody] CreateCustomerRequest createCustomerRequest)
{
ValidationResult result = await _validator.ValidateAsync(createCustomerRequest);
if (!result.IsValid)
{
return BadRequest(result);
}
//rest of the logic
return Created();
}
}
}
完成所有操作后,我们可以使用swagger测试验证器,我们可以在下面看到我们如何获得验证错误。
自定义验证器输出
我们可以轻松地为验证者编写单元测试,如下所示:
using BusinessLogic.Validators;
using FluentValidation.TestHelper;
namespace BusinessLogic.Tests
{
[TestFixture]
public class AddressRequestValidatorTests
{
private readonly AddressRequestValidator _validator = new AddressRequestValidator();
[Test]
public void GivenValidLine1_Should_Not_Have_Error()
{
var result = _validator.TestValidate(new Models.AddressRequest()
{
Line1 = "123 Main Street"
});
result.ShouldNotHaveValidationErrorFor(add => add.Line1);
}
[Test]
public void GivenInvalidZipCode_Should_Have_Error()
{
var result = _validator.TestValidate(new Models.AddressRequest()
{
PostalCode = "ABCDE"
});
result.ShouldHaveValidationErrorFor(add => add.PostalCode);
}
[Test]
public void GivenvalidZipCode_Should_Not_Have_Error()
{
var result = _validator.TestValidate(new Models.AddressRequest()
{
PostalCode = "78613"
});
result.ShouldNotHaveValidationErrorFor(add => add.PostalCode);
}
}
}
源代码获取:公众号回复消息【code:23084
】