Adicionando um Endpoint de Atualização PUT em .NET Aspire no Linux
[[academy-video-youtube({"vid": "hSRI_JKiH5M", "start_time": "0", "title": "Adding a PUT Update Endpoint in .NET Aspire on Linux", "creator": "Tim Corey", "length": "8m 43s"})]]
Uma vez que uma API pode ler e criar registros, a próxima operação é atualizar os existentes. Um endpoint PUT substitui o recurso inteiro pelos dados que o chamador fornece, o que significa que o corpo da solicitação precisa de todos os campos, não apenas aqueles que mudaram. Essa distinção entre PUT (substituição total) e PATCH (modificação parcial) é importante para como você projeta o tipo de entrada e como os chamadores interagem com o endpoint.
No vídeo "Adicionando um Endpoint PUT Update em .NET Aspire no Linux", Tim Corey adiciona o endpoint de atualização à API Tiny Ticket, cria um tipo de registro de atualização dedicado que inclui campos que o registro de inserção não tinha (como ID e data de conclusão), aplica atributos de validação e testa a viagem de ida e volta pelo Swagger. O episódio segue o mesmo padrão estabelecido em partes anteriores, mas introduz um campo nulo DateTime e a diferença entre as semânticas de PUT e PATCH. Se você está construindo endpoints CRUD em uma API mínima, este artigo cobre o lado da atualização.
Criando o Tipo de Registro de Atualização
[1:54 - 4:04] O registro de inserção do episódio anterior aceitou Título, Descrição e Prioridade. O registro de atualização precisa de dois campos adicionais: o ID do ticket sendo modificado e o timestamp DateCompleted. Tim copia o registro de inserção e ajusta-o.
public record TicketUpdateRecord(
[Required, Range(1, int.MaxValue)] int Id,
[Required, MinLength(1)] string Title,
[Required] string Description,
DateTime? DateCompleted,
[Range(1, 5)] int Priority
);
public record TicketUpdateRecord(
[Required, Range(1, int.MaxValue)] int Id,
[Required, MinLength(1)] string Title,
[Required] string Description,
DateTime? DateCompleted,
[Range(1, 5)] int Priority
);
Marcar Id como [Required] com uma restrição [Range(1, int.MaxValue)] impede que valores negativos ou zero cheguem ao banco de dados. DateCompleted é um DateTime? anulável porque um ticket que ainda não foi resolvido não deve exigir uma data de conclusão. Nenhum atributo de validação é necessário nele, já que nulo é um estado válido.
Para garantir que as propriedades do registro correspondam exatamente, Tim puxa a lista de campos do procedimento armazenado spTickets_Update. Esse alinhamento permite que o Dapper mapeie o registro diretamente sem nenhuma ligação manual de propriedade para parâmetro.
Mapeando o Endpoint PUT
[4:04 - 5:44] O registro do endpoint segue o padrão estabelecido. MapPut vincula-se à rota /api/tickets, e o manipulador chama o procedimento armazenado com o registro de atualização:
app.MapPut("/api/tickets", async Task<Results<NoContent, ValidationProblem>>
(TicketUpdateRecord ticket, ISqlDataAccess sql) =>
{
await sql.SaveDataAsync("dbo.spTickets_Update", ticket, "TicketDB");
return TypedResults.NoContent();
});
app.MapPut("/api/tickets", async Task<Results<NoContent, ValidationProblem>>
(TicketUpdateRecord ticket, ISqlDataAccess sql) =>
{
await sql.SaveDataAsync("dbo.spTickets_Update", ticket, "TicketDB");
return TypedResults.NoContent();
});
Declarar Results<NoContent, ValidationProblem> como o tipo de retorno informa ao framework que o endpoint produz 204 em caso de sucesso ou 400 se a validação falhar. A variante ValidationProblem é tratada automaticamente pelo pipeline registrado no episódio anterior; O próprio manipulador apenas precisa retornar o caso de sucesso.
Vale notar como o wrapper do Dapper mantém o acesso aos dados conciso: nome do procedimento armazenado, modelo, nome da string de conexão. Três parâmetros cobrem toda a chamada ao banco de dados. O wrapper foi escrito anteriormente na série e continua a se pagar à medida que cada novo endpoint o reutiliza sem modificação.
PUT vs. PATCH: Quando a Substituição Completa Importa
[6:06 - 6:46] Antes de testar, Tim pausa para esclarecer a diferença entre PUT e PATCH. Uma solicitação PUT substitui o recurso inteiro: cada campo no corpo da solicitação sobrescreve a coluna correspondente do banco de dados, mesmo que o chamador não tenha a intenção de mudar. Uma solicitação PATCH atualiza apenas os campos incluídos no corpo.
Para o projeto Tiny Ticket, PUT é a escolha certa, pois o front-end carregará o ticket completo, permitirá que o usuário edite os campos e enviará o objeto completo de volta. Em uma aplicação de produção, Tim menciona que ele provavelmente adicionaria um endpoint PATCH especificamente para operações comuns de campo único, como marcar um ticket como concluído, onde enviar o objeto inteiro apenas para alterar uma data parece desperdiçar recursos.
Testando a Atualização pelo Swagger
[6:46 - 8:26] Tim launches the API and opens Swagger. Antes de testar o PUT, ele executa o endpoint GET all para verificar o estado atual dos dados. Um dos registros de teste (ID 109) tem valores vazios para título, descrição e prioridade de testes anteriores. Isso se torna o alvo para a atualização.
Ele preenche o corpo da solicitação PUT com ID 109, um título de "Registro Exemplo", uma descrição e uma prioridade de 5. Após executar, a resposta retorna um 204. Executando GET all novamente confirma que o registro agora tem os valores atualizados.
Para verificar a validação, ele limpa o campo de título e executa novamente. A resposta retorna um 400 com uma mensagem de erro estruturada: "O campo de título do ticket é obrigatório." Os mesmos atributos de validação do endpoint de inserção são mantidos no registro de atualização porque usam o mesmo padrão de anotação.
Concluindo: Progresso do CRUD
[8:26 - 8:43] Com o endpoint PUT completo, a API Tiny Ticket agora cobre três das quatro operações CRUD: leitura (GET all e GET por ID), criação (POST) e atualização (PUT). Cada endpoint segue o mesmo padrão estrutural, o que torna a base de código previsível. A operação restante é DELETE, que Tim antecipa como o próximo episódio.
Conclusão
[8:38 - 8:43] Adicionar um endpoint PUT a uma API mínima requer um registro de atualização dedicado com atributos de validação, um registro MapPut com a URL da coleção e uma chamada de procedimento armazenado através do wrapper de acesso a dados. O tipo de retorno Results<NoContent, ValidationProblem> permite que o framework trate tanto as respostas de sucesso quanto as de falha de validação. Campos anuláveis como DateTime? passam sem exigir um atributo de validação, pois null é um valor válido para dados incompletos.
Navegação na série: Este artigo faz parte da série C# no Linux construindo o app Tiny Ticket. Anterior: Adicionando um Endpoint POST Insert. Próximo: Adicionando um Endpoint DELETE.
Dica de Exemplo: Se o seu procedimento armazenado de atualização retorna a contagem de linhas modificadas, verifique antes de retornar 204. Uma contagem de zero significa que o ID não correspondeu a nenhum registro, e você deve retornar um 404 em vez de ter sucesso silenciosamente.
Assista o vídeo completo no canal do YouTube dele e obtenha mais insights sobre como construir endpoints CRUD na série C# no Linux.
