PUT

PUT replaces an item with the one in the request body, its id taken from the address. The DAO updates the row and hands back the category as it was saved, and the action answers 200 OK with it, or 404 Not Found when there's no such item.

Journeyman · ASP.NET Core · REST API · Updated

After this lesson you can

  • Write a PUT action that updates an item and answers 200 OK with the updated item, or 404 Not Found when it isn't there

Key takeaways

  • A PUT to an item's address replaces it: the id comes from the address, everything else from the body.
  • OUTPUT INSERTED hands back the row as the UPDATE saved it, and Dapper's QuerySingleOrDefault reads it, or returns null when no row matched.
  • Return Ok(updated) for 200 OK with the updated item, or NotFound() for 404 when no row was updated.
  • Like a POST, a PUT answers with the saved item, so the client sees exactly what the database now holds.
  • PUT sends the whole item: leave the description out of the body, and it becomes null.

A client updates an item with a PUT to that item's address, sending the whole changed item as JSON in the body. The id comes from the address; everything else comes from the body. The server saves it and answers with the item as it's now stored, just as a POST does.

The DAO: update, and hand back what was saved

In POST, OUTPUT INSERTED.CategoryId handed back the new id from the INSERT. An UPDATE can use OUTPUT too: here it hands back the whole row, as it looks after the change. Dapper's QuerySingleOrDefault reads that row into a Category:

public Category? Update(int categoryId, Category category)
{
    string sql = """
        UPDATE Categories
        SET CategoryName = @CategoryName, Description = @Description
        OUTPUT INSERTED.CategoryId, INSERTED.CategoryName, INSERTED.Description
        WHERE CategoryId = @CategoryId;
        """;

    using SqlConnection connection = new SqlConnection(connectionString);
    return connection.QuerySingleOrDefault<Category>(sql, new { CategoryId = categoryId, category.CategoryName, category.Description });
}
  • OUTPUT INSERTED.… lists the columns to hand back. In an UPDATE, INSERTED means the row after the change.
  • An update of a category that doesn't exist changes no rows, so OUTPUT hands back nothing, and QuerySingleOrDefault returns null: the same null you met in GetById.
  • The anonymous object takes the id from the parameter, not from the body: the address is the one that says which category to change.

The action

[HttpPut("{id}")]
public ActionResult<Category> Update(int id, Category category)
{
    Category? updated = categoryDao.Update(id, category);
    if (updated is null)
    {
        return NotFound();
    }
    return Ok(updated);
}
  • [HttpPut("{id}")] answers PUT /api/categories/9.
  • int id comes from the address, Category category from the body.
  • ActionResult<Category>, as in POST: either a Category, or a status code result.
  • Ok(updated) answers 200 OK with the updated category in the body: "done, and here's what it looks like now."
  • NotFound() answers 404 when no row was updated.
PUT {{HostAddress}}/api/categories/9
Content-Type: application/json

{
  "categoryName": "Snacks",
  "description": "Chips, nuts, pretzels and popcorn"
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "categoryId": 9,
  "categoryName": "Snacks",
  "description": "Chips, nuts, pretzels and popcorn"
}

The same request to /api/categories/300 answers 404 Not Found. And because the body is a Category, the validation rules apply here too: a PUT with no categoryName gets the same automatic 400 as a POST.

Why send the item back?

The client just sent the category, so why answer with it? Because what the database saved can differ from what was sent: the body above has no categoryId (the address gave it), and in a real app a table often has columns the client never sends, such as a last-changed date the database sets itself. Answering with the saved item means the client shows exactly what's stored, without a second GET. That's why POST and PUT both answer with the item.

(Older APIs, and older tutorials, often answer a PUT with 204 No Content and an empty body. It still works, but answering 200 with the item is the common choice today.)

PUT replaces the whole item

A PUT sends the whole item, not just what changed. Send only a categoryName, and the description becomes null: the update sets every column from the body. That's what replace means, and the answer shows it: its description is null too.

(There's also a PATCH method for changing just some fields, but it's less common and needs more code. Most APIs use PUT.)

Check yourself

1 question to try on the lesson page.

So what

PUT is update-and-show: the address says which item, the body says what it should be now, and the answer is 200 with the item as it's saved, or 404 if there was nothing to update.

Key terms

Cheat sheets