ASP.NET Hosting

Selecting HTTP Verbs in Actual ASP.NET Core APIs: Why “Delete” Isn’t Always DELETE

Most explanations of HTTP verbs in ASP.NET Core Web API follow a simple mapping:

  • GET reads data
  • POST creates data
  • PUT updates data
  • DELETE removes data

This mapping is a useful starting point, but production APIs often encounter operations that do not fit neatly into a CRUD table.

Consider an HR or Payroll system that manages employee leave requests. When an employee clicks Withdraw Leave, the application may not actually delete the database record. Instead, it may change its status, record who performed the action, store the withdrawal timestamp, and preserve the original request for auditing.

In that situation, calling the operation “delete” from a user-interface perspective does not necessarily mean that the HTTP operation should be DELETE.

The important question is:

What does the API operation actually do to the resource?

The Standard HTTP Verb Mapping

A conventional ASP.NET Core controller might look like this:

[ApiController]
[Route("api/[controller]")]
public class LeaveRequestController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetLeaveRequest(int id)
    {
        return Ok(employeeDetails);
    }

    [HttpPost]
    public IActionResult CreateLeaveRequest(
        [FromBody] LeaveRequestDto request)
    {
        return Ok(result);
    }

    [HttpPut("{id}")]
    public IActionResult UpdateLeaveRequest(
        int id,
        [FromBody] LeaveRequestDto request)
    {
        return Ok(result);
    }

    [HttpDelete("{id}")]
    public IActionResult DeleteLeaveRequest(int id)
    {
        return Ok(result);
    }
}

This follows the familiar CRUD model:

GET     -> Read
POST    -> Create
PUT     -> Replace/Update
DELETE  -> Remove

These conventions are useful because clients, developers, API documentation tools, and infrastructure can understand the intended semantics more easily.

However, not every business operation is a simple CRUD operation.

When “Delete” Does Not Mean Physical Deletion

Suppose an employee submits this leave request:

LeaveRequestId: 10231
EmployeeId: EMP1023
FromDate:       2026-09-20
ToDate:         2026-09-22
Status:         Pending

The employee later decides to withdraw the request.

A simple implementation might physically remove the record:

DELETE FROM LeaveRequests
WHERE LeaveRequestId = 10231;

But that can be problematic in systems where the request forms part of an audit trail.

Instead, the application might retain the record:

LeaveRequestId: 10231
EmployeeId:     EMP1023
FromDate:       2026-09-20
ToDate:         2026-09-22
Status:         Withdrawn

The database operation is now an update rather than a deletion.

This distinction matters.

The UI may describe the operation as Delete Leave Request, while the API is actually performing a withdrawal or status transition.

Why Soft Deletes Are Common in Enterprise Systems

Keeping the original record can be useful for several reasons.

Auditability

Organizations may need to know what happened to a request and when.

For example:

Request Created
      |
      v
Manager Approved
      |
      v
Employee Withdrawn
      |
      v
Payroll Processed

Removing the database record makes reconstructing that history more difficult.

Historical Records

HR, attendance, expense, and payroll systems often depend on historical information.

A request that was withdrawn may still be relevant to understanding what happened during a particular payroll period.

Dispute Resolution

If an employee later questions a leave transaction, retaining the original request and its status history can help reconstruct the sequence of events.

For these reasons, many enterprise systems implement soft deletion or, more precisely, a business-state transition rather than physical deletion.

Model the Business State Explicitly

A simple model might be:

public enum LeaveRequestStatus
{
    Pending = 0,
    Approved = 1,
    Withdrawn = 2,
    Rejected = 3
}

public class LeaveRequest
{
    public int LeaveRequestId { get; set; }
    public string EmpId { get; set; } = string.Empty;

    public DateTime FromDate { get; set; }
    public DateTime ToDate { get; set; }

    public LeaveRequestStatus Status { get; private set; }

    public void Withdraw()
    {
        Status = LeaveRequestStatus.Withdrawn;
    }
}

Now the application is not pretending that the resource disappeared.

It is explicitly changing its business state.

That leads to an important API-design question:

Should a state transition be represented as PUT, PATCH, or POST?

PUT vs. PATCH for State Changes

PUT is generally used when the client provides a complete representation of the resource that should replace the current representation.

For example:

PUT /api/leave-requests/10231

The request might contain the complete leave request representation.

A partial status change is conceptually different:

PATCH /api/leave-requests/10231

For example:

{
  "status": "Withdrawn"
}

PATCH is therefore often a natural choice when the operation means:

Change part of the existing resource.

The corresponding ASP.NET Core endpoint could be:

[HttpPatch("{id}/status")]
public IActionResult UpdateStatus(
    int id,
    [FromBody] UpdateLeaveStatusRequest request)
{
    // Validate transition
    // Update status
    // Record audit information

    return Ok();
}

This makes the HTTP semantics clearer than calling a status change a DELETE.

When POST Makes Sense for a “Delete” Button

There are also cases where POST is appropriate.

Suppose the business operation is not simply changing a property but represents a domain action:

POST /api/leave-requests/10231/withdraw

The request means:

Execute the withdrawal operation for this leave request.

That operation might perform several actions:

  1. Validate whether withdrawal is allowed.
  2. Check the current approval state.
  3. Update the leave status.
  4. Record an audit entry.
  5. Reverse an attendance reservation.
  6. Trigger notifications.
  7. Update related workflow information.

This is more than a simple field update.

Using an action-oriented POST endpoint can therefore make the business operation explicit:

[HttpPost("{id}/withdraw")]
public IActionResult WithdrawLeaveRequest(int id)
{
    // Validate request state
    // Execute withdrawal workflow
    // Record audit information
    // Notify relevant users

    return Ok();
}

This is often easier to understand than:

DELETE /api/leave-requests/10231

when the record is not actually being deleted.

What About DELETE With a Request Body?

Another scenario occurs when an application wants to delete multiple records at once:

{
  "requestIds": [101, 102, 103]
}

A developer might consider:

DELETE /api/leave-requests

with those IDs in the request body.

Although HTTP does not categorically prohibit content on a DELETE request, the semantics of content in a DELETE request are not generally defined, which can create interoperability and tooling issues.

For batch operations, POST is often a more practical choice:

POST /api/leave-requests/batch-withdraw
{
  "requestIds": [101, 102, 103]
}

The important point is not that POST is a replacement for DELETE.

It is that the operation should be modeled according to its actual semantics.

The Endpoint Should Describe the Business Operation

Compare these two APIs:

DELETE /api/leave-requests/10231

and:

POST /api/leave-requests/10231/withdraw

The first communicates:

Remove this resource.

The second communicates:

Execute the withdrawal operation on this resource.

If the database record remains after the operation, the second API communicates the business behavior more accurately.

Similarly:

PATCH /api/leave-requests/10231/status

communicates:

Modify part of this resource’s state.

These distinctions make an API easier to understand and maintain.

A Practical HTTP Verb Guideline

A useful guideline for ASP.NET Core APIs is:

Verb Typical Use
GET Retrieve a resource or collection without changing server state
POST Create a resource or execute a domain/action operation
PUT Replace or fully update a known resource representation
PATCH Partially modify an existing resource
DELETE Remove a resource

The exact design depends on the API’s resource model and business semantics.

For example:

Physical deletion

DELETE /api/leave-requests/10231

Use this when the resource is genuinely being removed.

Partial status update

PATCH /api/leave-requests/10231/status

Use this when the resource remains but part of its state changes.

Domain operation

POST /api/leave-requests/10231/withdraw

Use this when withdrawal is a business operation involving validation, side effects, or workflow processing.

Batch operation

POST /api/leave-requests/batch-withdraw

Use this when the operation needs a structured payload containing multiple request identifiers or additional information.

Avoid Designing APIs Around UI Button Names

One common mistake is allowing the user-interface terminology to determine the HTTP verb.

A screen might contain:

[Edit] [Delete] [Approve] [Reject] [Withdraw]

These labels describe user actions, not necessarily HTTP semantics.

For example:

UI: Delete
Database: UPDATE Status = Withdrawn
API: POST /withdraw

Or:

UI: Edit Status
Database: UPDATE
API: PATCH

The API should model the underlying resource and business operation rather than mechanically copying the wording of the UI.

The Importance of Idempotency

Another consideration is idempotency.

GET, PUT, and DELETE are defined as idempotent methods, meaning that making the same request multiple times has the same intended effect on the server as making it once.

POST is not generally idempotent.

This distinction matters for operations such as withdrawal.

If the client sends:

POST /api/leave-requests/10231/withdraw

twice, the server should have a clear policy for the second request.

For example, it could return the current state or an appropriate conflict response rather than creating duplicate side effects.

For state changes where idempotent behavior is important, a carefully designed PUT or PATCH operation may be preferable.

The business semantics should determine the choice rather than simply selecting the shortest endpoint.

Takeaway

HTTP verbs are conventions for communicating the semantics of an API operation. The basic CRUD mapping remains an excellent starting point, but enterprise applications frequently contain business operations that require more precise modeling.

A leave request illustrates the difference clearly:

Physical deletion
        |
        v
DELETE

Partial state change
        |
        v
PATCH

Business operation such as withdrawal
        |
        v
POST

Complete resource replacement
        |
        v
PUT

A button labeled Delete does not automatically mean that the API should use DELETE.

If a leave request must remain in the database for auditing and the operation simply changes its state to Withdrawn, treating the operation as a state transition or domain action can better represent the actual behavior.

The key principle is:

Choose the HTTP method based on the semantics of the API operation, not merely the name of the UI action.

Understanding that distinction leads to APIs that are easier for clients to consume, easier for developers to maintain, and more accurately aligned with the business domain.

ASP.NET Core 10.0 Hosting Recommendation

HostForLIFE.eu
HostForLIFE.eu is a popular recommendation that offers various hosting choices. Starting from shared hosting to dedicated servers, you will find options fit for beginners and popular websites. It offers various hosting choices if you want to scale up. Also, you get flexible billing plans where you can choose to purchase a subscription even for one or six months.