DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Implementing CRUD Operations With NLP Using Microsoft.Extensions.AI
  • How to Merge Excel XLSX Files in Java
  • Spring Boot GoT: Game of Trace!
  • API Gateway Cache for POST Method

Trending

  • Evals in CI: How to Catch Agent Regressions Before Production Does
  • When Production Stops Moving: Running Claude Code Across a Distributed Enterprise Integration Team
  • Agentic Test Creation: From Plain-Language Requirements to End-to-End Test Cases
  • Event Sourcing in Distributed Systems: Architecture, Consistency, and Fault-Tolerant Projections

Draft This article hasn't been published, so readers can't see it.

In moderation This article is waiting for review, so readers can't see it yet.

Deleted This article has been deleted, so readers can't see it.

  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. GET, POST, PUT, DELETE: Choosing HTTP Verbs in a Real .NET Core Web API, Not Just by the Textbook

GET, POST, PUT, DELETE: Choosing HTTP Verbs in a Real .NET Core Web API, Not Just by the Textbook

Learn how to choose HTTP verbs in ASP.NET Core Web API, including POST-based soft deletes, batch operations, Angular integration, and idempotency.

By 
Dhana PAMULA
·
Nov. 05, 26 · Analysis
Post on X (opens in a new tab) Share on LinkedIn (opens in a new tab)
41 Views

Join the DZone community Save articles, join the discussion, and download Refcards and Trend Reports with a free account.

Join for free Already a member? Log in

Most explanations of HTTP verbs in ASP.NET Core Web API stop at the dictionary definition: GET reads, POST creates, PUT updates, DELETE removes. That's true, but it doesn't explain why real enterprise APIs often bend these rules — and understanding why matters more in practice than reciting the definitions. This article walks through HTTP verb selection using a real HR/Payroll workflow scenario: submitting, updating, and removing leave requests.

The Textbook Mapping

In a typical Web API controller, the four core verbs map to CRUD operations like this:

C#
 
[ApiController]
[Route("api/[controller]")]
public class LeaveRequestController : ControllerBase
{
    [HttpGet("get-emp-details")]
    public IActionResult GetEmployeeDetails(int finSlno, string empId, int createdBy)
    {
        // fetch and return data
        return Ok(employeeDetails);
    }

    [HttpPost("save-leave-request")]
    public IActionResult SaveLeaveRequest([FromBody] LeaveRequestDto request)
    {
        // create a new record
        return Ok(result);
    }

    [HttpPut("update-leave-request")]
    public IActionResult UpdateLeaveRequest([FromBody] LeaveRequestDto request)
    {
        // update an existing record
        return Ok(result);
    }

    [HttpDelete("delete-leave-request/{id}")]
    public IActionResult DeleteLeaveRequest(int id)
    {
        // remove a record
        return Ok(result);
    }
}


This is correct, and it's what most tutorials teach. But real enterprise systems — particularly HRMS and Payroll applications — frequently deviate from this pattern for reasons that only make sense once you understand what's actually happening to the data underneath.

Why "Delete" Often Isn't Really DELETE

Consider a leave request that an employee wants to withdraw. The textbook answer says: call [HttpDelete], remove the row from the database, done.

In practice, most HR and Payroll systems don't want a "delete" action to genuinely erase the record. There are real, practical reasons for this:

  • Audit trails – organizations often need to prove what happened and when, especially for anything touching attendance, payroll, or approvals. Permanently deleting a leave request destroys that history.
  • Compliance – payroll systems in particular are subject to regulations that may require retaining records for a defined period, regardless of whether the user considers them "deleted."
  • Traceability during disputes – if an employee claims a leave request was withdrawn incorrectly, having the original record (even if marked inactive) is the only way to investigate what actually happened.

Because of this, many systems implement what's called a soft delete: instead of removing the row, the operation updates a status field:

C#
 
public class LeaveRequest
{
    public int LeaveRequestId;
    public string EmpID;
    public int Status; // 0 = Pending, 1 = Approved, 2 = Withdrawn
}


A "delete" action becomes, in reality, an update to Status:

C#
 
[HttpPost("save-delete-role-mapping")]
public IActionResult SoftDeleteLeaveRequest([FromBody] List<LeaveRequestDto> requests)
{
    foreach (var req in requests)
    {
        req.Status = 2; // mark as withdrawn, don't remove the row
    }
    // save changes
    return Ok(result);
}


Because this is technically an update operation, not a true deletion, using [HttpPost] (or [HttpPut]) instead of [HttpDelete] is a defensible, common real-world choice — not a mistake or a shortcut. The verb should reflect what actually happens to the data, not just what the button on the screen says.

Why POST Sometimes Wins Even for Genuine Deletes

There's a second, more practical reason POST shows up in place of DELETE in real systems: payload complexity.

[HttpDelete] is traditionally designed around simple identifiers, often passed in the URL:

C#
 
[HttpDelete("delete-leave-request/{id}")]
public IActionResult Delete(int id)
{
    // remove a single record by id
    return Ok();
}


This works cleanly for deleting one record by one ID. But real operations are frequently batch operations — deleting or withdrawing multiple leave requests at once, each potentially needing additional context (who's performing the action, why, which workflow step it affects):

C#
 
[HttpPost("save-delete-role-mapping")]
public IActionResult BatchDelete([FromBody] List<LeaveRequestDto> requests)
{
    // process a batch of records, each carrying extra context
    return Ok();
}


Passing a list of complex objects in a DELETE request body is technically possible but goes against how DELETE has traditionally been used and supported across tooling. POST, which has always supported rich request bodies, handles this case more naturally — which is exactly why it's common to see "delete" operations implemented as POST endpoints in real enterprise codebases.

How This Looks From the Angular Side

The verb choice on the backend directly shapes how the frontend calls it. In an Angular service, each HTTP verb maps to a specific HttpClient method:

C#
 
// GET - fetching data, no body needed
getEmployeeDetails(empId: string): Observable<EmployeeDto> {
  return this.http.get<EmployeeDto>(`${this.apiUrl}/get-emp-details`, {
    params: new HttpParams().set('empId', empId)
  });
}

// POST - creating a new record, or a batch/complex operation
saveLeaveRequest(request: LeaveRequestDto): Observable<BaseResultModel<boolean>> {
  return this.http.post<any>(`${this.apiUrl}/save-leave-request`, request);
}

// POST used for a soft delete - batch of complex objects
saveDeleteWorkFlows(models: LeaveRequestDto[]): Observable<BaseResultModel<boolean>> {
  return this.http.post<any>(`${this.apiUrl}/save-delete-role-mapping`, models);
}


Notice that both the create and the soft-delete operations use .post() from the Angular side, mirroring the [HttpPost] decorator on the backend. This consistency matters: if the backend soft-deletes via POST but the Angular service tries to call .delete(), the request will fail or hit the wrong endpoint entirely, since Angular's HttpClient.delete() doesn't send a request body the same way .post() does by default. Keeping verb usage consistent across frontend and backend isn't just a style preference — mismatched verbs are a common, easy-to-miss source of failed API calls that can take a while to diagnose, since the error often looks like a routing problem rather than a verb mismatch.

A Note on Idempotency

A related concept worth understanding alongside verb selection is idempotency — whether calling the same operation multiple times produces the same result as calling it once.

GET, PUT, and DELETE are conventionally idempotent: fetching the same data twice returns the same result; updating a record to the same values twice leaves it in the same final state; deleting an already-deleted record (in systems that handle this gracefully) doesn't cause additional harm. POST is conventionally not idempotent — calling it twice can create two records instead of one.

This matters directly for the soft-delete pattern discussed above. If SoftDeleteLeaveRequest is implemented as a POST that simply sets Status = 2, calling it twice by accident (say, from a double-clicked button or a retried failed request) produces the same end state — the record is still just marked as withdrawn. This is a case where a POST endpoint behaves idempotently in practice, even though POST isn't required to be idempotent by convention. It's worth being deliberate about this: an endpoint that changes a status flag is safer to retry than one that increments a counter or appends to a list, and understanding which category an operation falls into helps predict how it will behave under real-world conditions like network retries or double submissions.

A Practical Rule of Thumb

None of this means HTTP verb conventions don't matter — they're still a useful default. The practical guidance is:

  • Use GET for anything that only reads data and has no side effects
  • Use POST for creating new records, and also for operations — including some deletes — that involve complex payloads or don't map cleanly to a single identifier
  • Use PUT for straightforward updates to an existing, fully-identified record
  • Use DELETE for simple, single-identifier removals where a true, permanent delete is actually intended

When you encounter an API that doesn't follow the textbook mapping exactly — like a "delete" endpoint implemented as POST — it's worth asking why, rather than assuming it's wrong. Often, as in the soft-delete case above, there's a real business reason: the data isn't actually being removed; it's being marked, and the verb reflects that reality rather than the UI label.

Takeaway

HTTP verb selection in a real Web API isn't just about matching a CRUD acronym to a decorator. It's about accurately representing what actually happens to the underlying data. A "delete" that's really a status update should probably be a POST or PUT, not a DELETE — and recognizing that distinction is a sign of understanding the system, not a shortcut around convention.

API Web API POST (HTTP)

Opinions expressed by DZone contributors are their own.

Related

  • Implementing CRUD Operations With NLP Using Microsoft.Extensions.AI
  • How to Merge Excel XLSX Files in Java
  • Spring Boot GoT: Game of Trace!
  • API Gateway Cache for POST Method

Partner Resources

×

Comments

Loading comments

The comments didn't load

Please try again in a moment.

Likes

Loading

The next likes didn't load. Please try again.

The likes didn't load

Please try again in a moment.

See who liked this

Log in or join DZone for free to see who liked this article.

Log in Join for free
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook