Validação de Dados em .NET 10 APIs Mínimas
APIs minimalistas sempre foram a alternativa enxuta ao ASP.NET Core baseado em controlador, mas por muito tempo tiveram uma lacuna notável: sem suporte embutido para validar dados recebidos. Você conectava verificações manuais em cada manipulador ou dependia de bibliotecas de terceiros. .NET 10 fecha essa lacuna com validação de anotação de dados de primeira classe para strings de consulta, cabeçalhos e corpos de solicitações, tudo isso sem pacotes extras.
Esta imersão profunda em APIs Minimalistas do .NET 10, com base na explicação de Tim Corey, explica como registrar o serviço de validação, anotar classes de modelo e evitar armadilhas comuns de modificadores de acesso que podem quebrar sua configuração.
A Configuração: Uma API Minimalista Simples
[0:44 - 1:35] A demonstração começa com um projeto de API Minimalista do ASP.NET Core bem enxuto rodando em .NET 10. A área de superfície é intencionalmente pequena: dois endpoints POST, cada um aceitando um modelo diferente.
app.MapPost("/person", (Person person) => Results.Ok(person));
app.MapPost("/login", (LoginModel login) => Results.Ok(login));
app.MapPost("/person", (Person person) => Results.Ok(person));
app.MapPost("/login", (LoginModel login) => Results.Ok(login));
O modelo Person carrega campos básicos de identidade. LoginModel lida com credenciais: um endereço de email, uma senha e um campo para confirmar a senha. Ambos são enviados como corpos JSON. Nesse ponto, não há nenhuma verificação de entrada; a API aceita qualquer coisa que recebe, incluindo strings vazias e endereços de e-mail malformados.
Scalar (a moderna UI OpenAPI que acompanha o .NET 10) é usada para enviar solicitações de teste diretamente do navegador, facilitando ver exatamente o que a API retorna antes e depois da validação.
Registrando o Serviço de Validação
[2:36 - 3:06] Antes que qualquer atributo de validação tenha efeito, você precisa optar pelo nível de serviço. Uma única chamada no bloco de registro de serviço habilita a imposição em todos os endpoints da API Minimalista:
builder.Services.AddValidation();
builder.Services.AddValidation();
Essa linha é todo o passo de configuração. Não há middleware para adicionar, nenhum estágio de pipeline para conectar. O framework assume automaticamente assim que o serviço é registrado. Se você pular essa chamada, seus atributos de anotação de dados estarão presentes no modelo, mas nunca avaliados, e as solicitações passam independentemente do que contêm.
Vale a pena manter isso em mente como o primeiro passo de depuração: se a validação não faz nada silenciosamente, AddValidation() é geralmente a peça que falta.
Adicionando Validação a um Modelo de Classe
[3:00 - 3:55] Com o serviço registrado, adicionar validação a um modelo baseado em classe é uma questão de decorar propriedades com atributos de anotação de dados:
public class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
public class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
Após adicionar a diretiva de uso System.ComponentModel.DataAnnotations no topo do arquivo, marcar FirstName e LastName como [Required] garante que eles sejam validados. Enviar uma solicitação POST com um corpo vazio através do Scalar agora retorna um 400 Bad Request com uma resposta de erro estruturada:
{
"errors": {
"FirstName": ["The FirstName field is required."],
"LastName": ["The LastName field is required."]
}
}
Sem tratamento de erro personalizado, sem atributos de filtro. O framework produz essa resposta automaticamente, e a verificação é executada antes que o corpo do seu manipulador seja executado, então você nunca precisa se proteger contra nulos dentro da lógica do endpoint.
Aplicando Validação a um Registro
[4:29 - 5:30] Os mesmos atributos funcionam em registros C#, mas a sintaxe difere ligeiramente porque as propriedades de registro são normalmente definidas no construtor primário em vez de como declarações de membros separadas.
public record LoginModel(
[Required] [EmailAddress] string Email,
[Required] string Password,
[Required] string ConfirmPassword
);
public record LoginModel(
[Required] [EmailAddress] string Email,
[Required] string Password,
[Required] string ConfirmPassword
);
Os atributos nos parâmetros do construtor são aplicados às propriedades geradas, então [Required] e [EmailAddress] se comportam exatamente como fariam em uma classe. Enviar uma solicitação com um email malformado, por exemplo "notanemail", agora retorna um 400 que identifica o campo Email como inválido.
O atributo [Compare] também pode ser usado para garantir que ConfirmPassword corresponda a Password. Em um registro, os alvos de atributo precisam de um empurrão explícito porque o compilador deve saber que você quer dizer o membro gerado, não o próprio parâmetro do construtor.
[property: Compare(nameof(Password))]
string ConfirmPassword
[property: Compare(nameof(Password))]
string ConfirmPassword
O alvo [property:] informa ao compilador para anexar o atributo ao membro gerado em vez do parâmetro. Sem ele, [Compare] compila, mas nunca é executado durante a verificação. Esta é a parte mais complicada de trabalhar com registros vs classes neste contexto: as propriedades de classe aceitam atributos naturalmente, enquanto os parâmetros de registro precisam do alvo explícito para qualquer coisa que opere no nível de membro.
A Requisito do Modificador de Acesso Público
[7:51 - 9:00] Uma armadilha comum é fácil de perder e não produz mensagem de erro quando você a encontra. O sistema de validação usa reflexão para inspecionar seus tipos de modelo em tempo de execução; para que a reflexão encontre os membros de um tipo, o próprio tipo deve ser marcado public.
// Validation will NOT run; the class is internal by default
class Person { ... }
// Validation runs correctly
public class Person { ... }
// Validation will NOT run; the class is internal by default
class Person { ... }
// Validation runs correctly
public class Person { ... }
A mesma regra se aplica a registros. Se o seu modelo for declarado sem um modificador de acesso, C# por padrão usa internal, e o serviço o ignora completamente. Seu endpoint ainda recebe a solicitação, o manipulador ainda executa, e nenhum erro é retornado; as checagens simplesmente não fazem nada em silêncio.
Este é um comportamento do ASP.NET Core, não específico para APIs Mínimas, mas aparece mais frequentemente aqui porque esses projetos tendem a ser compactos e os desenvolvedores às vezes definem modelos inline ou no mesmo arquivo que Program.cs sem pensar sobre a visibilidade.
Uma maneira rápida de auditar seu projeto: qualquer tipo de modelo passado para um manipulador de endpoint deve ser explicitamente marcado public. Se não for, nenhum número de atributos fará o framework disparar.
O Que Você Pode Validar
Os atributos de anotação de dados embutidos cobrem os cenários mais comuns sem qualquer trabalho extra:
[Required]rejeita valores nulos ou vazios[EmailAddress]valida o formato de uma string de email[Compare]verifica se duas propriedades correspondem, útil para confirmação de senha[Range]impõe limites numéricos ou de data[StringLength]limita o comprimento da string com um mínimo opcional[RegularExpression]valida contra um padrão personalizado
Todos estes funcionam em parâmetros de string de consulta, cabeçalhos de solicitação e corpos JSON. A mesma classe de modelo ou registro pode ser vinculada a partir de diferentes fontes sem alterar nenhum de seus atributos.
Conclusão
[7:46 - end] Com AddValidation() registrado e seus modelos decorados, as APIs Mínimas impõem restrições de entrada automaticamente em classes e registros iguais. Para que isso funcione no .NET 10, basta chamar builder.Services.AddValidation() uma vez, decorar as propriedades do seu modelo com atributos de anotação de dados padrão e garantir que cada tipo de modelo esteja marcado public.
O alvo [property:] em registros e o requisito do modificador public são as únicas pegadinhas reais. Eles são fáceis de esquecer e produzem falhas silenciosas em vez de erros de compilação, então mantenha-os em sua lista de verificação sempre que as verificações de entrada parecerem não fazer nada.
Assista ao vídeo completo no canal do Tim Corey no YouTube para a explicação completa do código-fonte.
