Skip to footer content
Iron Academy Logo
Learn C#
Learn C#

Other Categories

Adding a DELETE Endpoint in .NET Aspire on Linux

Tim Corey
8m 40s

Every CRUD API eventually needs a way to remove records, and the DELETE verb closes out the four core HTTP operations alongside GET, POST, and PUT. Compared to the other verbs, DELETE is structurally the simplest: no request body, no validation pipeline, no complex return type. What it does introduce is a design question that has no universally correct answer, namely what to return when the caller asks to delete a record that does not exist.

In his video "Adding a DELETE Endpoint in .NET Aspire on Linux," Tim Corey wraps up the Tiny Ticket API by adding the final endpoint, fixes a parameter casing inconsistency that had been quietly sitting in the GET-by-ID stored procedure, and discusses when to return a 404 versus a 204 for a missing record. The episode also previews the shift to the front end, which becomes the focus of the next phase of the C# on Linux series. If you have been following the series or are wiring up DELETE on a minimal API for the first time, this article walks through the full endpoint and the small refactor that made the parameter binding consistent across the project.

Mapping the DELETE Endpoint

[1:02 - 2:14] The endpoint registration follows the same shape as the other routes, with two adjustments. The route includes an {id:int} segment so the ID is passed in the URL rather than the body, and the handler signature uses MapDelete instead of MapPost or MapPut. There is no input record because nothing else is needed beyond the identifier.

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();
});

The handler calls the spTickets_Delete stored procedure through the Dapper wrapper, passing an anonymous object with the ID. Returning TypedResults.NoContent() produces a 204 status, signaling that the operation succeeded and there is no response body to return. The return type declaration mirrors the PUT endpoint from the previous episode, since both operations have the same set of possible outcomes from the framework's perspective.

Fixing the Parameter Casing Mismatch

[2:14 - 4:32] While wiring up the DELETE call, Tim notices an inconsistency he introduced earlier in the series. The spTickets_Delete stored procedure uses an uppercase Id parameter, which means the anonymous object needs an explicit Id = id assignment. The spTickets_Update procedure also uses uppercase Id. But spTickets_Get, the procedure behind the GET-by-ID endpoint, uses a lowercase id. That lowercase variant let the original handler pass new { id } without the explicit assignment, which felt convenient at the time but left the codebase inconsistent.

Rather than carry the asymmetry forward, he opens SQL Server Management Studio and alters the GET procedure to use uppercase Id:

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

With the procedure updated, the GET handler in Program.cs now needs the same explicit mapping the DELETE and PUT handlers use, changing from new { id } to new { Id = id }. The change is mechanical, but the reasoning matters: consistent parameter casing across stored procedures means every endpoint binds parameters the same way, which removes a small but real source of confusion when reading the data access layer later. A convention that only holds in one of four places is not a convention.

When to Return 204 vs. 404 on a Missing Record

[4:46 - 5:46] After the endpoint compiles, Tim pauses on a design question that comes up with every DELETE implementation. If the caller passes an ID that does not exist, what should the API return? Two reasonable answers exist.

Returning 204 NoContent regardless of whether a row was deleted treats the request as idempotent. From the caller's perspective, the resource is gone, which was the goal. This is what the current handler does, and it is what the Tiny Ticket project will ship with. Returning 404 NotFound for a missing record gives the caller more information but requires the stored procedure to report whether a row was actually deleted, typically by returning a row count that the handler can inspect before deciding which response to send.

For an internal CRUD API where the front end already knows which IDs exist (because it just loaded the list), 204 is fine. For a public API where callers might guess IDs, 404 prevents the silent illusion that data has been removed when it never existed. Tim notes that returning 404 can leak information about which IDs exist in the database, though for a delete operation the practical risk is low since exercising the endpoint already implies write access.

Testing Through Swagger

[5:46 - 7:08] With the database running, Tim launches the API and opens Swagger. He starts with GET all to take stock of the current data: records 1, 2, 3 from the original seed, plus 107, 109, and 110 left over from earlier insert testing.

He executes DELETE on 107 and gets back a 204. Same for 110. To verify the missing-record behavior, he runs DELETE on 1011, an ID that was never in the database. The response is still 204, with no indication that nothing was deleted. That is the trade-off discussed in the previous section, now visible in the actual API response.

A second GET all confirms the final state: records 1, 2, 3, and 109 remain. The DELETE endpoint works for valid IDs and fails silently for invalid ones, exactly as the implementation specifies.

Wrapping Up: CRUD Complete

[7:08 - 8:38] Adding DELETE completes the four CRUD verbs for the Tiny Ticket API. The same structural pattern carries through every endpoint: route definition, stored procedure name, Dapper data access call, typed result. Tim is candid that a production API would likely add more endpoints, such as a PATCH for marking a ticket completed without sending the entire object across the wire, or a dedicated search endpoint. The goal of the series, however, is to keep each layer focused so the next layer (the front end) has a clean surface to call against.

Consistency is what makes the project comfortable to read. The Dapper wrapper, the POST insert pattern, the validation pipeline, and the typed results all combine so that every new endpoint takes roughly the same amount of code regardless of which verb it implements. That predictability is what makes the API a pleasant target for the front end work that follows.

Conclusion

[8:38 - 8:40] Adding a DELETE endpoint to a minimal API requires a MapDelete registration with an ID segment in the route, a stored procedure call through the data access wrapper, and a TypedResults.NoContent() return. The endpoint completes the CRUD surface for the Tiny Ticket project and sets up the shift to the front end in the next phase of the series.

Series navigation: This article is part of the C# on Linux series building the Tiny Ticket app. Previous: Adding a PUT Update Endpoint. Next phase: front end pages that consume the API.

Example Tip: If you want a more informative DELETE response without changing the stored procedure, capture the affected row count from SaveDataAsync and return a TypedResults.NotFound() when the count is zero. This adds the 404 path without restructuring the data access layer.

Watch full video on his YouTube Channel and gain more insights on building CRUD endpoints in the C# on Linux series.

Hero Worlddot related to Adding a DELETE Endpoint in .NET Aspire on Linux
Hero Affiliate related to Adding a DELETE Endpoint in .NET Aspire on Linux

Earn More by Sharing What You Love

Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Iron Support Team

We're online 24 hours, 5 days a week.
Chat
Email
Call Me