Start Debugging

A validação assíncrona chega às Minimal APIs com o .NET 11 Preview 6

O Preview 6 adiciona AsyncValidationAttribute e IAsyncValidatableObject para que as regras de DataAnnotations consultem o banco de dados antes de o seu endpoint rodar, sem bloquear uma thread.

O .NET 11 Preview 6 fecha uma lacuna que o DataAnnotations carrega desde o lançamento: a validação era síncrona. Se uma regra precisava consultar o banco de dados (este e-mail já está em uso, este horário ainda está livre?), você bloqueava uma thread dentro do IsValid ou abandonava o DataAnnotations e partia para o FluentValidation. O Preview 6 adiciona validadores assíncronos de primeira classe e os conecta à validação integrada de Minimal APIs introduzida no .NET 10.

Os dois novos ganchos

Dois tipos surgem em System.ComponentModel.DataAnnotations. O primeiro é AsyncValidationAttribute, para uma regra sobre um único membro:

public sealed class UniqueEmailAttribute : AsyncValidationAttribute
{
    // Still required, but throws if the attribute is async-only.
    protected override ValidationResult? IsValid(object? value, ValidationContext context)
        => throw new InvalidOperationException("Use IsValidAsync.");

    protected override async Task<ValidationResult?> IsValidAsync(
        object? value, ValidationContext context, CancellationToken cancellationToken)
    {
        var db = context.GetRequiredService<AppDbContext>();
        var exists = await db.Users.AnyAsync(u => u.Email == (string?)value, cancellationToken);
        return exists ? new ValidationResult("Email is already registered.") : ValidationResult.Success;
    }
}

O segundo é IAsyncValidatableObject, para regras que abrangem várias propriedades ou precisam do objeto inteiro. Ele retorna um IAsyncEnumerable<ValidationResult>, e como estende IValidatableObject você ainda implementa o Validate síncrono (que lança uma exceção quando você valida apenas de forma assíncrona):

public class ReservationRequest : IAsyncValidatableObject
{
    public DateOnly Date { get; set; }
    public int PartySize { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext context)
        => throw new InvalidOperationException("Use ValidateAsync.");

    public async IAsyncEnumerable<ValidationResult> ValidateAsync(
        ValidationContext context,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        var db = context.GetRequiredService<AppDbContext>();
        var taken = await db.Reservations.CountAsync(r => r.Date == Date, cancellationToken);
        if (taken + PartySize > 40)
            yield return new ValidationResult("No capacity left for that date.", [nameof(PartySize)]);
    }
}

Como configurar

Não há nova ativação além da chamada AddValidation() que o .NET 10 já introduziu. Registre-a, e o framework executa seus validadores assíncronos antes de o corpo do endpoint ser executado:

builder.Services.AddValidation();

app.MapPost("/reservations", (ReservationRequest request) => Results.Ok(request));

Uma requisição inválida é interrompida com um 400 e um payload ValidationProblemDetails, exatamente como no caminho síncrono. Por baixo dos panos, isso se apoia na nova API Validator.ValidateObjectAsync das bibliotecas base, então também funciona fora do ASP.NET Core.

O detalhe que vale a pena conhecer

A validação assíncrona convida a idas e voltas em série por acidente, então o framework executa o trabalho de forma concorrente quando pode: atributos assíncronos no mesmo membro começam juntos, e os itens de uma coleção são validados em paralelo. Ainda assim preserva a ordem existente entre membro, tipo e IValidatableObject, de modo que uma checagem barata de [Required] falha rápido antes de você pagar por uma consulta ao banco de dados. Combine isso com a proteção CSRF automática do Preview 6 e o pipeline integrado cobre muito mais terreno do que cobria uma versão atrás.

Baixe o SDK do .NET 11 Preview 6, mire em net11.0 e leia as notas de versão do ASP.NET Core para conhecer toda a superfície de validadores.

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Voltar