aspnet-core-validacion-modelos-data-annotations-fluentvalidation

Validación de modelos con Data Annotations y FluentValidation

  • 4 min

La validación de modelos es el proceso de comprobar que los datos recibidos cumplen las reglas esperadas.

Una regla básica del desarrollo web es no confiar en los datos del cliente. Da igual si llegan desde tu aplicación de React, una app móvil o una integración externa: todo lo que entra por la red puede ser incorrecto o malicioso.

Si esperas un precio, te enviarán un negativo. Si esperas un email, te enviarán “hola”. Si esperas una fecha, te enviarán el texto de El Quijote.

Para evitar que estos datos basura corrompan nuestra base de datos o causen excepciones, necesitamos una capa de validación.

En .NET tenemos varias formas de hacerlo. Vamos a ver la validación nativa con Data Annotations y una alternativa basada en FluentValidation.

Data Annotations

Esta es la forma que viene integrada en .NET. Consiste en decorar las propiedades de nuestros DTOs con atributos que definen las reglas.

Es muy cómodo porque la lógica de validación vive junto a la definición de los datos.

Ejemplo práctico

using System.ComponentModel.DataAnnotations;

public class CrearUsuarioDto
{
    [Required(ErrorMessage = "El nombre es obligatorio")]
    [StringLength(50, MinimumLength = 3)]
    public string Nombre { get; set; }

    [Required]
    [EmailAddress] // Valida formato [email protected]
    public string Email { get; set; }

    [Range(18, 99)]
    public int Edad { get; set; }

    [RegularExpression(@"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$",
        ErrorMessage = "La contraseña debe tener letras y números")]
    public string Password { get; set; }
}
Copied!

¿Cómo se ejecuta?

Si usas [ApiController] en tus controladores, no tienes que hacer nada. El framework valida el objeto automáticamente antes de entrar al método. Si alguna regla no se cumple, devuelve instantáneamente un 400 Bad Request con la lista de errores.

Si no usas [ApiController], debes verificar el estado del modelo manualmente:

if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}
Copied!

FluentValidation

Data Annotations está bien para cosas simples, pero tiene problemas:

  1. Ensucia tus DTOs: Mezclas definición de datos con reglas de negocio.
  2. Lógica limitada: Es difícil validar cosas como “El campo X es obligatorio solo si el campo Y vale true”.
  3. Difícil de testear: Probar atributos es engorroso.

Para resolver esto, muchos proyectos .NET usan la librería FluentValidation.

Instalación

dotnet add package FluentValidation
dotnet add package FluentValidation.DependencyInjectionExtensions
Copied!

Separando la lógica

Con FluentValidation, creamos una clase separada (un Validador) para cada DTO. El DTO queda limpio y puro.

using FluentValidation;

public class CrearUsuarioValidator : AbstractValidator<CrearUsuarioDto>
{
    public CrearUsuarioValidator()
    {
        RuleFor(x => x.Nombre)
            .NotEmpty().WithMessage("El nombre no puede estar vacío")
            .Length(3, 50);

        RuleFor(x => x.Email)
            .NotEmpty()
            .EmailAddress();

        // Reglas condicionales complejas
        RuleFor(x => x.TarjetaCredito)
            .NotEmpty()
            .When(x => x.MetodoPago == "Tarjeta")
            .WithMessage("Si pagas con tarjeta, necesitamos el número");

        // Validaciones personalizadas
        RuleFor(x => x.Edad)
            .Must(SerMayorDeEdad).WithMessage("Debes ser mayor de edad");
    }

    private bool SerMayorDeEdad(int edad)
    {
        return edad >= 18;
    }
}
Copied!

Registro en Program.cs

Registramos los validadores en el contenedor de dependencias:

builder.Services.AddValidatorsFromAssemblyContaining<Program>();
Copied!

Después podemos inyectar IValidator<CrearUsuarioDto> y ejecutar la validación donde corresponda.

La integración automática antigua de FluentValidation.AspNetCore con el pipeline MVC ya no es la recomendación principal para proyectos nuevos. Además, no funciona con Minimal APIs. Para código moderno, suele ser más claro usar validación manual, endpoint filters o una integración específica basada en filtros.

Data Annotations vs FluentValidation: ¿Cuál elegir?

CaracterísticaData AnnotationsFluentValidation
ComplejidadMuy bajaMedia
UbicaciónEn el propio DTO (Atributos)En clase separada (SRP)
PotenciaBásica (formatos, rangos)Alta (lógica condicional, consultas, reglas compuestas)
Inyección de dependenciasLimitadaPermite inyectar servicios en el validador
OrganizaciónReglas junto al DTOReglas en una clase separada
  • Para prototipos rápidos o validaciones triviales (Required), usa Data Annotations.
  • Para reglas condicionales o validadores que necesitan dependencias, FluentValidation puede mantener el código más ordenado y fácil de probar.

Validar en Minimal APIs

En Minimal APIs no tenemos la validación automática de [ApiController]. Si usamos FluentValidation, podemos inyectar el validador y ejecutarlo explícitamente:

app.MapPost("/usuarios", async (CrearUsuarioDto dto, IValidator<CrearUsuarioDto> validator) =>
{
    var resultado = await validator.ValidateAsync(dto);

    if (!resultado.IsValid)
    {
        return Results.ValidationProblem(resultado.ToDictionary());
    }

    // Lógica de guardado...
    return Results.Ok();
});
Copied!

Desde .NET 10, Minimal APIs también pueden validar Data Annotations de forma integrada al registrar builder.Services.AddValidation(). Si el modelo no es válido, el endpoint devuelve un 400 Bad Request antes de ejecutar el handler.