Skip to content
Merged
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