Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 79 additions & 2 deletions aspnetcore/tutorials/min-web-api.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
---
title: "Tutorial: Create a Minimal API with ASP.NET Core"
author: wadepickett
description: Learn how to build a Minimal API with ASP.NET Core.
description: Learn how to build a minimal API with ASP.NET Core.
ai-usage: ai-assisted
ms.author: wpickett
ms.date: 07/29/2024
ms.custom: engagement-fy24
ms.date: 02/12/2026
monikerRange: '>= aspnetcore-6.0'
uid: tutorials/min-web-api
---
Expand Down Expand Up @@ -33,6 +34,7 @@ This tutorial creates the following API:
| `GET /todoitems/{id}` | Get an item by ID | None | To-do item |
| `POST /todoitems` | Add a new item | To-do item | To-do item |
| `PUT /todoitems/{id}` | Update an existing item   | To-do item | None |
| `PATCH /todoitems/{id}` | Partially update an item  | Partial to-do item | None |
| `DELETE /todoitems/{id}`     | Delete an item     | None | None |

## Prerequisites
Expand Down Expand Up @@ -513,6 +515,81 @@ Use Swagger to send a PUT request:

---

## Examine the PATCH endpoint

Create a file named `TodoPatchDto.cs` with the following code:

:::code language="csharp" source="~/tutorials/min-web-api/samples/9.x/todo/TodoPatchDto.cs":::

The `TodoPatchDto` class uses nullable properties (`string?` and `bool?`) to distinguish between a field that wasn't provided in the request versus a field explicitly set to a value.

The sample app implements a single PATCH endpoint using `MapPatch`:

[!code-csharp[](~/tutorials/min-web-api/samples/9.x/todo/Program.cs?name=snippet_patch)]

This method is similar to the `MapPut` method, except it uses HTTP PATCH and only updates the fields provided in the request. A successful response returns [204 (No Content)](https://www.rfc-editor.org/rfc/rfc9110#status.204). According to the HTTP specification, a PATCH request enables partial updates, allowing clients to send only the fields that need to be changed.

The PATCH endpoint uses a `TodoPatchDto` class with nullable properties to properly handle partial updates. Using nullable properties allows the endpoint to distinguish between a field that wasn't provided (null) versus a field explicitly set to a value (including false for boolean fields). Without nullable properties, a non-nullable bool would default to false, potentially overwriting an existing true value when that field wasn't included in the request.

> [!NOTE]
> PATCH operations allow partial updates to resources. For more advanced partial updates using JSON Patch documents, see <xref:web-api/jsonpatch>.

## Test the PATCH endpoint

This sample uses an in-memory database that must be initialized each time the app is started. There must be an item in the database before you make a PATCH call. Call GET to ensure there's an item in the database before making a PATCH call.

Update only the `name` property of the to-do item that has `Id = 1` and set its name to `"run errands"`.

# [Visual Studio](#tab/visual-studio)

* In **Endpoints Explorer**, right-click the **PATCH** endpoint, and select **Generate request**.

The following content is added to the `TodoApi.http` file:

```http
PATCH {{TodoApi_HostAddress}}/todoitems/{id}

###
```

* In the PATCH request line, replace `{id}` with `1`.

* Add the following lines immediately after the PATCH request line:

```http
Content-Type: application/json

{
"name": "run errands"
}
```

The preceding code adds a Content-Type header and a JSON request body with only the field to update.

* Select the **Send request** link that is above the new PATCH request line.

The PATCH request is sent to the app and the response is displayed in the **Response** pane. The response body is empty, and the status code is 204.

# [Visual Studio Code](#tab/visual-studio-code)

Use Swagger to send a PATCH request:

* Select **Patch /todoitems/{id}** > **Try it out**.

* Set the **id** field to `1`.

* Set the request body to the following JSON:

```json
{
"name": "run errands"
}
```

* Select **Execute**.

---

## Examine and test the DELETE endpoint

The sample app implements a single DELETE endpoint using `MapDelete`:
Expand Down
42 changes: 42 additions & 0 deletions aspnetcore/tutorials/min-web-api/includes/min-web-api6-7.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This tutorial creates the following API:
| `GET /todoitems/{id}` | Get an item by ID | None | To-do item |
| `POST /todoitems` | Add a new item | To-do item | To-do item |
| `PUT /todoitems/{id}` | Update an existing item &nbsp; | To-do item | None |
| `PATCH /todoitems/{id}` | Partially update an item &nbsp;| Partial to-do item | None |
| `DELETE /todoitems/{id}` &nbsp; &nbsp; | Delete an item &nbsp; &nbsp; | None | None |

## Prerequisites
Expand Down Expand Up @@ -320,6 +321,47 @@ Use Swagger to send a PUT request:

* Select **Execute**.

## Examine the PATCH endpoint

Create a file named `TodoPatchDto.cs` with the following code:

:::code language="csharp" source="~/tutorials/min-web-api/samples/7.x/todo/TodoPatchDto.cs":::

The `TodoPatchDto` class uses nullable properties (`string?` and `bool?`) to distinguish between a field that wasn't provided in the request versus a field explicitly set to a value.

The sample app implements a single PATCH endpoint using `MapPatch`:

[!code-csharp[](~/tutorials/min-web-api/samples/7.x/todo/Program.cs?name=snippet_patch)]

This method is similar to the `MapPut` method, except it uses HTTP PATCH and only updates the fields provided in the request. A successful response returns [204 (No Content)](https://www.rfc-editor.org/rfc/rfc9110#status.204). According to the HTTP specification, a PATCH request enables partial updates, allowing clients to send only the fields that need to be changed.

The PATCH endpoint uses a `TodoPatchDto` class with nullable properties to properly handle partial updates. Using nullable properties allows the endpoint to distinguish between a field that wasn't provided (null) versus a field explicitly set to a value (including false for boolean fields). Without nullable properties, a non-nullable bool would default to false, potentially overwriting an existing true value when that field wasn't included in the request.

> [!NOTE]
> PATCH operations allow partial updates to resources. For more advanced partial updates using JSON Patch documents, see <xref:web-api/jsonpatch>.

## Test the PATCH endpoint

This sample uses an in-memory database that must be initialized each time the app is started. There must be an item in the database before you make a PATCH call. Call GET to ensure there's an item in the database before making a PATCH call.

Update only the `name` property of the to-do item that has `Id = 1` and set its name to `"run errands"`.

Use Swagger to send a PATCH request:

* Select **Patch /todoitems/{id}** > **Try it out**.

* Set the **id** field to `1`.

* Set the request body to the following JSON:

```json
{
"name": "run errands"
}
```

* Select **Execute**.

## Examine and test the DELETE endpoint

The sample app implements a single DELETE endpoint using `MapDelete`:
Expand Down
76 changes: 76 additions & 0 deletions aspnetcore/tutorials/min-web-api/includes/min-web-api8.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This tutorial creates the following API:
| `GET /todoitems/{id}` | Get an item by ID | None | To-do item |
| `POST /todoitems` | Add a new item | To-do item | To-do item |
| `PUT /todoitems/{id}` | Update an existing item &nbsp; | To-do item | None |
| `PATCH /todoitems/{id}` | Partially update an item &nbsp;| Partial to-do item | None |
| `DELETE /todoitems/{id}` &nbsp; &nbsp; | Delete an item &nbsp; &nbsp; | None | None |

## Prerequisites
Expand Down Expand Up @@ -491,6 +492,81 @@ Use Swagger to send a PUT request:

---

## Examine the PATCH endpoint

Create a file named `TodoPatchDto.cs` with the following code:

:::code language="csharp" source="~/tutorials/min-web-api/samples/8.x/todo/TodoPatchDto.cs":::

The `TodoPatchDto` class uses nullable properties (`string?` and `bool?`) to distinguish between a field that wasn't provided in the request versus a field explicitly set to a value.

The sample app implements a single PATCH endpoint using `MapPatch`:

[!code-csharp[](~/tutorials/min-web-api/samples/8.x/todo/Program.cs?name=snippet_patch)]

This method is similar to the `MapPut` method, except it uses HTTP PATCH and only updates the fields provided in the request. A successful response returns [204 (No Content)](https://www.rfc-editor.org/rfc/rfc9110#status.204). According to the HTTP specification, a PATCH request enables partial updates, allowing clients to send only the fields that need to be changed.

The PATCH endpoint uses a `TodoPatchDto` class with nullable properties to properly handle partial updates. Using nullable properties allows the endpoint to distinguish between a field that wasn't provided (null) versus a field explicitly set to a value (including false for boolean fields). Without nullable properties, a non-nullable bool would default to false, potentially overwriting an existing true value when that field wasn't included in the request.

> [!NOTE]
> PATCH operations allow partial updates to resources. For more advanced partial updates using JSON Patch documents, see <xref:web-api/jsonpatch>.

## Test the PATCH endpoint

This sample uses an in-memory database that must be initialized each time the app is started. There must be an item in the database before you make a PATCH call. Call GET to ensure there's an item in the database before making a PATCH call.

Update only the `name` property of the to-do item that has `Id = 1` and set its name to `"run errands"`.

# [Visual Studio](#tab/visual-studio)

* In **Endpoints Explorer**, right-click the **PATCH** endpoint, and select **Generate request**.

The following content is added to the `TodoApi.http` file:

```http
PATCH {{TodoApi_HostAddress}}/todoitems/{id}

###
```

* In the PATCH request line, replace `{id}` with `1`.

* Add the following lines immediately after the PATCH request line:

```http
Content-Type: application/json

{
"name": "run errands"
}
```

The preceding code adds a Content-Type header and a JSON request body with only the field to update.

* Select the **Send request** link that is above the new PATCH request line.

The PATCH request is sent to the app and the response is displayed in the **Response** pane. The response body is empty, and the status code is 204.

# [Visual Studio Code](#tab/visual-studio-code)

Use Swagger to send a PATCH request:

* Select **Patch /todoitems/{id}** > **Try it out**.

* Set the **id** field to `1`.

* Set the request body to the following JSON:

```json
{
"name": "run errands"
}
```

* Select **Execute**.

---

## Examine and test the DELETE endpoint

The sample app implements a single DELETE endpoint using `MapDelete`:
Expand Down
73 changes: 72 additions & 1 deletion aspnetcore/tutorials/min-web-api/samples/7.x/todo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#define FINAL // MINIMAL FINAL TYPEDR
#define FINAL // MINIMAL FINAL WITHPATCH TYPEDR
#if MINIMAL
// <snippet_min>
var builder = WebApplication.CreateBuilder(args);
Expand Down Expand Up @@ -77,6 +77,77 @@ is Todo todo

app.Run();
// </snippet_all>
#elif WITHPATCH
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);
});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
});

// <snippet_patch>
app.MapPatch("/todoitems/{id}", async (int id, TodoPatchDto inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

if (inputTodo.Name is not null) todo.Name = inputTodo.Name;
if (inputTodo.IsComplete is not null) todo.IsComplete = inputTodo.IsComplete.Value;

await db.SaveChangesAsync();

return Results.NoContent();
});
// </snippet_patch>

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}

return Results.NotFound();
});

app.Run();
#elif TYPEDR
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public class TodoPatchDto
{
public string? Name { get; set; }
public bool? IsComplete { get; set; }
}
Loading