Ir para o conteúdo do rodapé
Iron Academy Logo
Aprenda C#
Aprenda C#

Outras categorias

Adicionando um Endpoint DELETE em .NET Aspire no Linux

[[academy-video-youtube({"vid": "x10CYBXrLxg", "start_time": "0", "title": "Adding a DELETE Endpoint in .NET Aspire on Linux", "creator": "Tim Corey", "length": "8m 40s"})]]

Toda API CRUD eventualmente precisa de um meio para remover registros, e o verbo DELETE fecha as quatro operações principais do HTTP junto com GET, POST e PUT. Comparado aos outros verbos, DELETE é estruturalmente o mais simples: sem corpo de requisição, sem pipeline de validação, sem tipo de retorno complexo. O que ele apresenta é uma questão de design que não tem uma resposta universalmente correta, nomeadamente o que retornar quando o chamador solicita deletar um registro que não existe.

Em seu vídeo "Adding a DELETE Endpoint in .NET Aspire on Linux", Tim Corey conclui a API Tiny Ticket adicionando o endpoint final, corrige uma inconsistência de capitalização de parâmetro que estava silenciosamente presente no procedimento armazenado GET-by-ID, e discute quando retornar um 404 versus um 204 para um registro ausente. O episódio também antecipa a transição para a parte frontal, que se torna o foco da próxima fase da série C# on Linux. Se você está acompanhando a série ou conectando DELETE em uma API mínima pela primeira vez, este artigo percorre todo o endpoint e a pequena refatoração que tornou a vinculação do parâmetro consistente em todo o projeto.

Mapeando o Endpoint DELETE

[1:02 - 2:14] O registro do endpoint segue a mesma forma das outras rotas, com dois ajustes. A rota inclui um segmento {id:int} para que o ID seja passado na URL em vez do corpo, e a assinatura do handler usa MapDelete em vez de MapPost ou MapPut. Não há registro de entrada porque nada mais é necessário além do identificador.

app.MapDelete("/api/tickets/{id:int}",
    async Task<Results<NoContent, ValidationProblem>>
    (ISqlDataAccess sql, int id) =>
{
    await sql.SaveDataAsync("dbo.spTickets_Delete",
        new { Id = id }, "TicketDB");
    return TypedResults.NoContent();
});
app.MapDelete("/api/tickets/{id:int}",
    async Task<Results<NoContent, ValidationProblem>>
    (ISqlDataAccess sql, int id) =>
{
    await sql.SaveDataAsync("dbo.spTickets_Delete",
        new { Id = id }, "TicketDB");
    return TypedResults.NoContent();
});

O handler chama o procedimento armazenado spTickets_Delete através do wrapper Dapper, passando um objeto anônimo com o ID. Retornar TypedResults.NoContent() produz um status 204, sinalizando que a operação foi bem-sucedida e não há corpo de resposta para retornar. A declaração do tipo de retorno espelha o endpoint PUT do episódio anterior, já que ambas as operações têm o mesmo conjunto de possíveis resultados da perspectiva do framework.

Corrigindo a Inconsistência de Capitalização de Parâmetro

[2:14 - 4:32] Ao conectar a chamada DELETE, Tim nota uma inconsistência que ele introduziu anteriormente na série. O procedimento armazenado spTickets_Delete usa um parâmetro Id em maiúsculas, o que significa que o objeto anônimo precisa de uma atribuição explícita Id = id. O procedimento spTickets_Update também usa Id em maiúsculas. Mas spTickets_Get, o procedimento por trás do endpoint GET-by-ID, usa id em minúsculas. Essa variante em minúsculas permitiu que o handler original passasse new { id } sem a atribuição explícita, o que parecia conveniente na época, mas deixou a base de código inconsistente.

Em vez de levar a assimetria adiante, ele abre o SQL Server Management Studio e altera o procedimento GET para usar Id em maiúsculas:

ALTER PROCEDURE spTickets_Get
    @Id int
AS
BEGIN
    SELECT Id, Title, Description, DateCompleted, Priority, CreatedDate
    FROM dbo.Tickets
    WHERE Id = @Id;
END

Com o procedimento atualizado, o handler GET em Program.cs agora precisa do mesmo mapeamento explícito que os handlers DELETE e PUT usam, mudando de new { id } para new { Id = id }. A mudança é mecânica, mas a razão importa: consistência de capitalização de parâmetro em procedimentos armazenados significa que cada endpoint vincula parâmetros da mesma maneira, o que remove uma pequena mas real fonte de confusão ao ler a camada de acesso a dados mais tarde. Uma convenção que só se mantém em um de quatro lugares não é uma convenção.

Quando Retornar 204 vs. 404 em um Registro Faltante

[4:46 - 5:46] Após o endpoint compilar, Tim faz uma pausa em uma questão de design que surge com cada implementação DELETE. Se o chamador passar um ID que não existe, o que a API deve retornar? Existem duas respostas razoáveis.

Retornar 204 NoContent independentemente de uma linha ter sido deletada trata a requisição como idempotente. Da perspectiva do chamador, o recurso se foi, o que era o objetivo. É o que o manipulador atual faz, e é com isso que o projeto Tiny Ticket será entregue. Retornar 404 NotFound para um registro ausente dá mais informações ao chamador, mas requer que o procedimento armazenado reporte se uma linha foi realmente deletada, tipicamente retornando uma contagem de linhas que o manipulador pode inspecionar antes de decidir qual resposta enviar.

Para uma API CRUD interna onde a parte frontal já sabe quais IDs existem (porque acabou de carregar a lista), 204 está bem. Para uma API pública onde os chamadores possam chutar IDs, 404 previne a ilusão silenciosa de que dados foram removidos quando nunca existiram. Tim observa que retornar 404 pode vazar informações sobre quais IDs existem no banco de dados, embora para uma operação de exclusão o risco prático seja baixo, já que exercitar o endpoint já implica acesso de escrita.

Testando Através do Swagger

[5:46 - 7:08] Com o banco de dados em execução, Tim lança a API e abre Swagger. Ele começa com GET todos para ter uma noção dos dados atuais: registros 1, 2, 3 da seed original, mais 107, 109 e 110 que sobraram de testes de inserção anteriores.

Ele executa DELETE em 107 e recebe um 204 de volta. O mesmo para 110. Para verificar o comportamento de registro ausente, ele executa DELETE em 1011, um ID que nunca esteve no banco de dados. A resposta ainda é 204, sem indicação de que nada foi deletado. Eis aí o dilema discutido na seção anterior, agora visível na resposta real da API.

Um segundo GET todos confirma o estado final: registros 1, 2, 3 e 109 permanecem. O endpoint DELETE funciona para IDs válidos e falha silenciosamente para inválidos, exatamente como a implementação especifica.

Concluindo: CRUD Completo

[7:08 - 8:38] Adicionar o DELETE completa os quatro verbos CRUD para a API Tiny Ticket. O mesmo padrão estrutural se mantem através de cada endpoint: definição de rota, nome do procedimento armazenado, chamada de acesso a dados pelo Dapper, resultado tipado. Tim é franco que uma API de produção provavelmente adicionaria mais endpoints, como um PATCH para marcar um ticket concluído sem enviar o objeto inteiro pela rede, ou um endpoint de busca dedicado. O objetivo da série, entretanto, é manter cada camada focada para que a próxima camada (a parte frontal) tenha uma superfície limpa contra a qual chamar.

Consistência é o que torna o projeto confortável de ler. O wrapper Dapper, o padrão de inserção POST, o pipeline de validação, e os resultados tipados todos se combinam de tal forma que cada novo endpoint leva aproximadamente a mesma quantidade de código, independentemente de qual verbo ele implementa. Essa previsibilidade é o que torna a API um alvo prazeroso para o trabalho frontal que segue.

Conclusão

[8:38 - 8:40] Adicionar um endpoint DELETE a uma API mínima requer um registro MapDelete com um segmento ID na rota, uma chamada de procedimento armazenado através do wrapper de acesso a dados e um retorno TypedResults.NoContent(). O endpoint completa a superfície CRUD para o projeto Tiny Ticket e prepara o caminho para a mudança para a parte frontal na próxima fase da série.

Navegação da série: Este artigo é parte da série C# on Linux construindo o aplicativo Tiny Ticket. Anterior: Adicionando um Endpoint PUT Update. Próxima fase: páginas da parte frontal que consomem a API.

Dica de exemplo: Se desejar uma resposta DELETE mais informativa sem mudar o procedimento armazenado, capture a contagem de linhas afetadas de SaveDataAsync e retorne um TypedResults.NotFound() quando a contagem for zero. Isso adiciona o caminho 404 sem reestruturar a camada de acesso a dados.

Assista o vídeo completo vídeo no YouTube Canal dele e obtenha mais insights sobre construção de endpoints CRUD na série C# on Linux.

Hero Worlddot related to Adicionando um Endpoint DELETE em .NET Aspire no Linux
Hero Affiliate related to Adicionando um Endpoint DELETE em .NET Aspire no Linux

Ganhe mais compartilhando o que você ama.

Você cria conteúdo para desenvolvedores que trabalham com .NET, C#, Java, Python ou Node.js? Transforme sua expertise em renda extra!

Equipe de Suporte Iron

Estamos online 24 horas por dia, 5 dias por semana.
Bater papo
E-mail
Liga para mim