Skip to content

Commit f7014be

Browse files
Copilotwadepickett
andauthored
Add PATCH endpoint documentation to minimal API tutorial (#36350)
* Initial plan * Add PATCH endpoint to minimal API tutorial for .NET 7, 8, and 9 Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com> * Update metadata for min-web-api.md with ai-usage and current date Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com> * Remove nuget.exe and update .gitignore * Fix code block language specifiers in min-web-api8.md to use http Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com> * Fix PATCH implementation to use DTO with nullable properties for correct partial update semantics Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com> * Apply suggestions from code review ms.date update * Separate PATCH code into WITHPATCH block and add TodoPatchDto.cs creation instructions - Removed snippet_patch from FINAL block in all Program.cs files (7.x, 8.x, 9.x) - Added new WITHPATCH block containing complete code with PATCH endpoint - Updated #define comment to include WITHPATCH - Added instructions to create TodoPatchDto.cs file before PATCH section in all markdown files - This ensures readers create the DTO before pasting code that references it Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com> Co-authored-by: Wade Pickett <wpickett@microsoft.com>
1 parent 2e14c2e commit f7014be

9 files changed

Lines changed: 428 additions & 5 deletions

File tree

aspnetcore/tutorials/min-web-api.md

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
---
22
title: "Tutorial: Create a Minimal API with ASP.NET Core"
33
author: wadepickett
4-
description: Learn how to build a Minimal API with ASP.NET Core.
4+
description: Learn how to build a minimal API with ASP.NET Core.
5+
ai-usage: ai-assisted
56
ms.author: wpickett
6-
ms.date: 07/29/2024
77
ms.custom: engagement-fy24
8+
ms.date: 02/12/2026
89
monikerRange: '>= aspnetcore-6.0'
910
uid: tutorials/min-web-api
1011
---
@@ -33,6 +34,7 @@ This tutorial creates the following API:
3334
| `GET /todoitems/{id}` | Get an item by ID | None | To-do item |
3435
| `POST /todoitems` | Add a new item | To-do item | To-do item |
3536
| `PUT /todoitems/{id}` | Update an existing item &nbsp; | To-do item | None |
37+
| `PATCH /todoitems/{id}` | Partially update an item &nbsp;| Partial to-do item | None |
3638
| `DELETE /todoitems/{id}` &nbsp; &nbsp; | Delete an item &nbsp; &nbsp; | None | None |
3739

3840
## Prerequisites
@@ -513,6 +515,81 @@ Use Swagger to send a PUT request:
513515

514516
---
515517

518+
## Examine the PATCH endpoint
519+
520+
Create a file named `TodoPatchDto.cs` with the following code:
521+
522+
:::code language="csharp" source="~/tutorials/min-web-api/samples/9.x/todo/TodoPatchDto.cs":::
523+
524+
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.
525+
526+
The sample app implements a single PATCH endpoint using `MapPatch`:
527+
528+
[!code-csharp[](~/tutorials/min-web-api/samples/9.x/todo/Program.cs?name=snippet_patch)]
529+
530+
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.
531+
532+
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.
533+
534+
> [!NOTE]
535+
> PATCH operations allow partial updates to resources. For more advanced partial updates using JSON Patch documents, see <xref:web-api/jsonpatch>.
536+
537+
## Test the PATCH endpoint
538+
539+
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.
540+
541+
Update only the `name` property of the to-do item that has `Id = 1` and set its name to `"run errands"`.
542+
543+
# [Visual Studio](#tab/visual-studio)
544+
545+
* In **Endpoints Explorer**, right-click the **PATCH** endpoint, and select **Generate request**.
546+
547+
The following content is added to the `TodoApi.http` file:
548+
549+
```http
550+
PATCH {{TodoApi_HostAddress}}/todoitems/{id}
551+
552+
###
553+
```
554+
555+
* In the PATCH request line, replace `{id}` with `1`.
556+
557+
* Add the following lines immediately after the PATCH request line:
558+
559+
```http
560+
Content-Type: application/json
561+
562+
{
563+
"name": "run errands"
564+
}
565+
```
566+
567+
The preceding code adds a Content-Type header and a JSON request body with only the field to update.
568+
569+
* Select the **Send request** link that is above the new PATCH request line.
570+
571+
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.
572+
573+
# [Visual Studio Code](#tab/visual-studio-code)
574+
575+
Use Swagger to send a PATCH request:
576+
577+
* Select **Patch /todoitems/{id}** > **Try it out**.
578+
579+
* Set the **id** field to `1`.
580+
581+
* Set the request body to the following JSON:
582+
583+
```json
584+
{
585+
"name": "run errands"
586+
}
587+
```
588+
589+
* Select **Execute**.
590+
591+
---
592+
516593
## Examine and test the DELETE endpoint
517594

518595
The sample app implements a single DELETE endpoint using `MapDelete`:

aspnetcore/tutorials/min-web-api/includes/min-web-api6-7.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This tutorial creates the following API:
1515
| `GET /todoitems/{id}` | Get an item by ID | None | To-do item |
1616
| `POST /todoitems` | Add a new item | To-do item | To-do item |
1717
| `PUT /todoitems/{id}` | Update an existing item &nbsp; | To-do item | None |
18+
| `PATCH /todoitems/{id}` | Partially update an item &nbsp;| Partial to-do item | None |
1819
| `DELETE /todoitems/{id}` &nbsp; &nbsp; | Delete an item &nbsp; &nbsp; | None | None |
1920

2021
## Prerequisites
@@ -320,6 +321,47 @@ Use Swagger to send a PUT request:
320321

321322
* Select **Execute**.
322323

324+
## Examine the PATCH endpoint
325+
326+
Create a file named `TodoPatchDto.cs` with the following code:
327+
328+
:::code language="csharp" source="~/tutorials/min-web-api/samples/7.x/todo/TodoPatchDto.cs":::
329+
330+
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.
331+
332+
The sample app implements a single PATCH endpoint using `MapPatch`:
333+
334+
[!code-csharp[](~/tutorials/min-web-api/samples/7.x/todo/Program.cs?name=snippet_patch)]
335+
336+
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.
337+
338+
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.
339+
340+
> [!NOTE]
341+
> PATCH operations allow partial updates to resources. For more advanced partial updates using JSON Patch documents, see <xref:web-api/jsonpatch>.
342+
343+
## Test the PATCH endpoint
344+
345+
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.
346+
347+
Update only the `name` property of the to-do item that has `Id = 1` and set its name to `"run errands"`.
348+
349+
Use Swagger to send a PATCH request:
350+
351+
* Select **Patch /todoitems/{id}** > **Try it out**.
352+
353+
* Set the **id** field to `1`.
354+
355+
* Set the request body to the following JSON:
356+
357+
```json
358+
{
359+
"name": "run errands"
360+
}
361+
```
362+
363+
* Select **Execute**.
364+
323365
## Examine and test the DELETE endpoint
324366

325367
The sample app implements a single DELETE endpoint using `MapDelete`:

aspnetcore/tutorials/min-web-api/includes/min-web-api8.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This tutorial creates the following API:
1515
| `GET /todoitems/{id}` | Get an item by ID | None | To-do item |
1616
| `POST /todoitems` | Add a new item | To-do item | To-do item |
1717
| `PUT /todoitems/{id}` | Update an existing item &nbsp; | To-do item | None |
18+
| `PATCH /todoitems/{id}` | Partially update an item &nbsp;| Partial to-do item | None |
1819
| `DELETE /todoitems/{id}` &nbsp; &nbsp; | Delete an item &nbsp; &nbsp; | None | None |
1920

2021
## Prerequisites
@@ -491,6 +492,81 @@ Use Swagger to send a PUT request:
491492

492493
---
493494

495+
## Examine the PATCH endpoint
496+
497+
Create a file named `TodoPatchDto.cs` with the following code:
498+
499+
:::code language="csharp" source="~/tutorials/min-web-api/samples/8.x/todo/TodoPatchDto.cs":::
500+
501+
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.
502+
503+
The sample app implements a single PATCH endpoint using `MapPatch`:
504+
505+
[!code-csharp[](~/tutorials/min-web-api/samples/8.x/todo/Program.cs?name=snippet_patch)]
506+
507+
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.
508+
509+
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.
510+
511+
> [!NOTE]
512+
> PATCH operations allow partial updates to resources. For more advanced partial updates using JSON Patch documents, see <xref:web-api/jsonpatch>.
513+
514+
## Test the PATCH endpoint
515+
516+
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.
517+
518+
Update only the `name` property of the to-do item that has `Id = 1` and set its name to `"run errands"`.
519+
520+
# [Visual Studio](#tab/visual-studio)
521+
522+
* In **Endpoints Explorer**, right-click the **PATCH** endpoint, and select **Generate request**.
523+
524+
The following content is added to the `TodoApi.http` file:
525+
526+
```http
527+
PATCH {{TodoApi_HostAddress}}/todoitems/{id}
528+
529+
###
530+
```
531+
532+
* In the PATCH request line, replace `{id}` with `1`.
533+
534+
* Add the following lines immediately after the PATCH request line:
535+
536+
```http
537+
Content-Type: application/json
538+
539+
{
540+
"name": "run errands"
541+
}
542+
```
543+
544+
The preceding code adds a Content-Type header and a JSON request body with only the field to update.
545+
546+
* Select the **Send request** link that is above the new PATCH request line.
547+
548+
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.
549+
550+
# [Visual Studio Code](#tab/visual-studio-code)
551+
552+
Use Swagger to send a PATCH request:
553+
554+
* Select **Patch /todoitems/{id}** > **Try it out**.
555+
556+
* Set the **id** field to `1`.
557+
558+
* Set the request body to the following JSON:
559+
560+
```json
561+
{
562+
"name": "run errands"
563+
}
564+
```
565+
566+
* Select **Execute**.
567+
568+
---
569+
494570
## Examine and test the DELETE endpoint
495571

496572
The sample app implements a single DELETE endpoint using `MapDelete`:

aspnetcore/tutorials/min-web-api/samples/7.x/todo/Program.cs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#define FINAL // MINIMAL FINAL TYPEDR
1+
#define FINAL // MINIMAL FINAL WITHPATCH TYPEDR
22
#if MINIMAL
33
// <snippet_min>
44
var builder = WebApplication.CreateBuilder(args);
@@ -77,6 +77,77 @@ is Todo todo
7777

7878
app.Run();
7979
// </snippet_all>
80+
#elif WITHPATCH
81+
using Microsoft.EntityFrameworkCore;
82+
83+
var builder = WebApplication.CreateBuilder(args);
84+
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
85+
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
86+
var app = builder.Build();
87+
88+
app.MapGet("/todoitems", async (TodoDb db) =>
89+
await db.Todos.ToListAsync());
90+
91+
app.MapGet("/todoitems/complete", async (TodoDb db) =>
92+
await db.Todos.Where(t => t.IsComplete).ToListAsync());
93+
94+
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
95+
await db.Todos.FindAsync(id)
96+
is Todo todo
97+
? Results.Ok(todo)
98+
: Results.NotFound());
99+
100+
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
101+
{
102+
db.Todos.Add(todo);
103+
await db.SaveChangesAsync();
104+
105+
return Results.Created($"/todoitems/{todo.Id}", todo);
106+
});
107+
108+
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
109+
{
110+
var todo = await db.Todos.FindAsync(id);
111+
112+
if (todo is null) return Results.NotFound();
113+
114+
todo.Name = inputTodo.Name;
115+
todo.IsComplete = inputTodo.IsComplete;
116+
117+
await db.SaveChangesAsync();
118+
119+
return Results.NoContent();
120+
});
121+
122+
// <snippet_patch>
123+
app.MapPatch("/todoitems/{id}", async (int id, TodoPatchDto inputTodo, TodoDb db) =>
124+
{
125+
var todo = await db.Todos.FindAsync(id);
126+
127+
if (todo is null) return Results.NotFound();
128+
129+
if (inputTodo.Name is not null) todo.Name = inputTodo.Name;
130+
if (inputTodo.IsComplete is not null) todo.IsComplete = inputTodo.IsComplete.Value;
131+
132+
await db.SaveChangesAsync();
133+
134+
return Results.NoContent();
135+
});
136+
// </snippet_patch>
137+
138+
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
139+
{
140+
if (await db.Todos.FindAsync(id) is Todo todo)
141+
{
142+
db.Todos.Remove(todo);
143+
await db.SaveChangesAsync();
144+
return Results.NoContent();
145+
}
146+
147+
return Results.NotFound();
148+
});
149+
150+
app.Run();
80151
#elif TYPEDR
81152
using Microsoft.AspNetCore.Http.HttpResults;
82153
using Microsoft.EntityFrameworkCore;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public class TodoPatchDto
2+
{
3+
public string? Name { get; set; }
4+
public bool? IsComplete { get; set; }
5+
}

0 commit comments

Comments
 (0)