Projeto de exemplo que demonstra a implementação de validações robustas e fluentes em aplicações .NET usando a biblioteca FluentValidation. Utiliza uma API de pagamentos como caso de uso prático para ilustrar a criação de validadores, regras customizadas e integração com ASP.NET Core.
- Integração do FluentValidation com ASP.NET Core 8.0
- Criação de classes de validação para DTOs (Data Transfer Objects)
- Uso de regras de validação encadeadas e fluentes
- Implementação de validadores customizados para regras de negócio específicas (CPF/CNPJ)
- Validação de campos seguindo regras de negócio brasileiras
- Injeção de dependência para validadores
- Deploy containerizado com Docker
O projeto organiza os validadores no projeto Application
, seguindo a estrutura do domínio:
Application/
└── Validators/
└── Payment/
├── BankAccountRequestValidator.cs
└── GeneratePaymentRequestValidator.cs
Cada DTO possui sua própria classe de validação, que herda de AbstractValidator<T>
.
As regras são definidas de forma declarativa e legível no construtor de cada validador. O projeto inclui exemplos de:
- Regras básicas:
NotEmpty()
,Length()
,GreaterThan()
. - Regras de formato:
Matches("^[0-9]*$")
para garantir que um campo contenha apenas dígitos. - Validadores aninhados: Um validador pode reutilizar outro para validar propriedades complexas (
SetValidator(new BankAccountRequestValidator())
). - Validadores de enumeração:
IsInEnum()
para garantir que o valor corresponda a um membro de umEnum
. - Validadores customizados: O projeto inclui um validador customizado para CPF/CNPJ, demonstrando a extensibilidade da biblioteca.
A integração é feita de forma transparente no Program.cs
:
- Os validadores são registrados no contêiner de injeção de dependência com
AddValidatorsFromAssemblyContaining<T>()
. - O middleware do FluentValidation é habilitado com
AddFluentValidationAutoValidation()
.
Com isso, o ASP.NET Core automaticamente intercepta as requisições, localiza o validador apropriado para o DTO e executa a validação antes de o controller ser acionado. Em caso de falha, retorna uma resposta 400 Bad Request
com os detalhes dos erros.
- FluentValidation - Biblioteca para validação de objetos
- Extensions.FluentValidation.Br - Extensões para validação de CPF/CNPJ
- ASP.NET Core 8.0 - Framework para a API de exemplo
- Docker - Containerização da aplicação
- Clean Architecture - Separação de responsabilidades
- .NET 8 SDK
- Docker e Docker Compose (opcional, para execução em container)
# Navegue até a pasta da solução
cd Solution
# Execute a WebApi
cd WebApi
dotnet run
A API estará disponível em http://localhost:5192
(ou outra porta, verifique o output do console).
docker-compose up -d
Isso irá iniciar a API na porta 5192
.
Acesse http://localhost:5192/swagger
para explorar os endpoints. Tente enviar uma requisição para POST /api/payment
com dados inválidos para ver o FluentValidation em ação.
Exemplo de corpo de requisição inválido:
{
"originAccount": {
"accountNumber": "123",
"accountHolder": "",
"bankCode": "1234",
"branchCode": "123",
"documentNumber": "111.111.111-11"
},
"destinationAccount": {
"accountNumber": "12345",
"accountHolder": "Jane Doe",
"bankCode": "001",
"branchCode": "1234",
"documentNumber": "12.345.678/0001-99"
},
"amount": 0,
"methodId": 99
}
Resposta esperada (400 Bad Request):
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"OriginAccount.AccountNumber": [
"Account number must be between 5 and 12 digits."
],
"OriginAccount.AccountHolder": [
"Account holder is required."
],
"OriginAccount.BankCode": [
"Bank code must be 3 digits."
],
"OriginAccount.BranchCode": [
"Branch code must be 4 digits."
],
"Amount": [
"Amount must be greater than zero."
],
"MethodId": [
"Invalid payment method."
]
}
}
// Application/Validators/Payment/BankAccountRequestValidator.cs
public class BankAccountRequestValidator : AbstractValidator<BankAccountRequest>
{
public BankAccountRequestValidator()
{
RuleFor(x => x.AccountNumber)
.NotEmpty()
.Length(5, 12)
.Matches("^[0-9]*$")
.WithMessage("Account number must be between 5 and 12 digits.");
RuleFor(x => x.BankCode)
.NotEmpty()
.Length(3)
.Matches("^[0-9]*$")
.WithMessage("Bank code must be 3 digits.");
RuleFor(x => x.DocumentNumber)
.NotEmpty()
.IsValidCpfOrCnpj()
.WithMessage("Document number must be a valid CPF or CNPJ.");
}
}
// Application/Validators/Custom/CustomValidators.cs
public static class CustomValidators
{
public static IRuleBuilderOptions<T, string> IsValidCpfOrCnpj<T>(this IRuleBuilder<T, string> ruleBuilder)
{
return ruleBuilder.Must(document =>
{
if (string.IsNullOrEmpty(document)) return false;
var doc = new string(document.Where(char.IsDigit).ToArray());
if (doc.Length == 11) return IsCpf(doc);
if (doc.Length == 14) return IsCnpj(doc);
return false;
}).WithMessage("Invalid CPF or CNPJ.");
}
// ... métodos privados IsCpf e IsCnpj
}
// WebApi/Extensions/Payment/PaymentDependencyInjection.cs
public static IServiceCollection AddPaymentServices(this IServiceCollection services)
{
// ... outros serviços
services.AddValidatorsFromAssemblyContaining<GeneratePaymentRequestValidator>();
return services;
}
// WebApi/Program.cs
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddPaymentServices();
O projeto segue Clean Architecture com separação clara de responsabilidades:
Solution/
├── Domain/ # Regras de negócio e entidades
├── Application/ # Casos de uso e validação
│ ├── UseCases/ # Lógica de aplicação
│ ├── DTOs/ # Objetos de transferência
│ └── Validators/ # Classes de validação
├── Infrastructure/ # Implementações técnicas
├── WebApi/ # API REST
└── Shared/ # Utilitários compartilhados
Benefícios desta arquitetura:
- Validadores desacoplados dos controllers e da lógica de negócio.
- Testabilidade facilitada.
- Reutilização de validadores.