Skip to content

Comments

Add PATCH endpoint documentation to minimal API tutorial#36350

Open
Copilot wants to merge 10 commits intomainfrom
copilot/add-patch-example-tutorial
Open

Add PATCH endpoint documentation to minimal API tutorial#36350
Copilot wants to merge 10 commits intomainfrom
copilot/add-patch-example-tutorial

Conversation

Copy link
Contributor

Copilot AI commented Nov 13, 2025

  • Explore the repository structure
  • Understand the tutorial file structure and moniker ranges
  • Review the issue requirements
  • Add PATCH endpoint to overview table for .NET 7+ versions
  • Add PATCH endpoint code examples to sample projects (7.x, 8.x, 9.x)
  • Add PATCH testing instructions for Visual Studio
  • Add PATCH testing instructions for Visual Studio Code
  • Update documentation with PATCH notes and cross-references
  • Update metadata (ai-usage, ms.date)
  • Verify all code snippets are referenced correctly
  • Clean up accidentally committed files
  • Fix inconsistent code block language specifiers
  • Fix PATCH implementation with TodoPatchDto to handle partial updates correctly
  • Separate PATCH code using #define blocks to prevent compile errors
  • Add TodoPatchDto.cs creation instructions before PATCH section
  • Final review and testing
Original prompt

This section details on the original issue you should resolve

<issue_title>Add PATCH to tutorial</issue_title>
<issue_description>### Description

Verbatim:

It doesn't have example of a PATCH operation. Microsoft added MapPatch support in .net7 but didn't added an example of it in this documentation page. It would be useful if Microsoft add an example which uses MapPut and JsonPatch in the net core template for minimal api.

Page URL

https://learn.microsoft.com/en-us/aspnet/core/tutorials/min-web-api?view=aspnetcore-8.0&tabs=visual-studio

Content source URL

https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/tutorials/min-web-api.md

Document ID

ab408035-6313-a183-9940-32de00e0e977

Article author

@wadepickett</issue_description>

Comments on the Issue (you are @copilot in this section)

@wadepickett AI analysis test run by wadepickett Prompt: verify-issue-set-course-of-action_v.05.md Model: GPT-4 Nov 2023 -------

Issue Analysis: Add PATCH to tutorial

Issue Summary

This is a valid issue. The user reports that the minimal API tutorial for ASP.NET Core does not include an example for a PATCH operation, specifically using MapPatch and (optionally) JsonPatchDocument in the .NET minimal API template. PATCH support was introduced in .NET 7 but is not demonstrated in the current tutorial.

Issue Context and Validation

  • Article evaluated: Tutorial: Create a minimal API with ASP.NET Core

  • Source file path: aspnetcore/tutorials/min-web-api.md

  • Current status:

    • No PATCH Example: The table and main API walkthrough provide GET, POST, PUT, DELETE; no PATCH endpoint is included for updating partial resources.
    • No mention of MapPatch or JsonPatch: The file does not show usage (sample code or instruction) for PATCH-specific minimal API patterns, nor how to apply JSON Patch documents.
    • Tutorial scope and audience (minimal API, .NET 6–9): The visible section of the tutorial is geared to basics and practical "CRUD" with PUT and POST, but PATCH is now a canonical REST verb and should be covered at least in .NET 7+ coverage.
  • Recent related/overlapping issues:

    • Numerous closed or open issues on PATCH, MapPatch, and JsonPatch in the repo—but primarily in advanced topics, Blazor articles, or dedicated jsonpatch.md.
    • This tutorial still does not describe PATCH in the visual walkthrough or add a PATCH endpoint for minimal APIs.
  • No effective workaround or cross-reference: The article does not redirect readers to dedicated PATCH or JsonPatch documentation, so discoverability in the tutorial context is poor.

  • Has the issue already been addressed?

    • No. As of the latest HEAD, there is no PATCH sample or mention in the tutorial—confirmed by direct review of the min-web-api.md at HEAD and the accompanying sample code.

Affected File(s) and Section(s)

  • File to update:

    • aspnetcore/tutorials/min-web-api.md
  • Sections impacted:

    • "Overview" Table: Add PATCH endpoint row
    • API Implementation Steps: Add PATCH endpoint code (ideally after PUT section)
    • Testing steps: Add PATCH example in .http file (VS/VS Code) and Swagger instructions

Recommended Documentation Update

  1. Add PATCH endpoint to "Overview" Table:

    • Describe PATCH operation, request/response body requirements, and intent (partial resource update).
  2. Provide PATCH minimal API sample code:

    • Add MapPatch endpoint to the example, updating partial fields for a todo item.
    • If using .NET 7+, optionally add an example using JsonPatchDocument<Todo> for advanced partial updates.
  3. Update step-by-step instructions:

    • Include PATCH request sample (in .http file and in Swagger UI testing steps).
    • Demonstrate use and limitations of PATCH as compared to PUT.
  4. Add a tip/note block:

    • e.g., [!NOTE] PATCH provides partial updates and can leverage JsonPatch for complex scenarios.
  5. Reference dedicated guidance:

Example Code or CLI Snippets (if applicable)

Basic PATCH with direct property assignment (Recommended for tutorial simplicity):

todoItems.MapPatch("/{id}", async (int id, TodoPatchDto patch, TodoDb db) =>
{
    var todoItem = await db.Todos.FindAsync(id);
    if (todoItem is null) return Results.NotFound();
    if (patch.Name is not null) todoItem.Name = patch.Name;
...

</details>

- Fixes dotnet/AspNetCore.Docs#33789

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).


<!-- PREVIEW-TABLE-START -->

---

#### Internal previews

| 📄 File | 🔗 Preview link |
|:--|:--|
| [aspnetcore/tutorials/min-web-api.md](https://github.com/dotnet/AspNetCore.Docs/blob/afd0e4f194fb61d509fe751cce775f64077f91e6/aspnetcore/tutorials/min-web-api.md) | [Tutorial: Create a Minimal API with ASP.NET Core](https://review.learn.microsoft.com/en-us/aspnet/core/tutorials/min-web-api?branch=pr-en-us-36350) |


<!-- PREVIEW-TABLE-END -->

Copilot AI and others added 3 commits November 13, 2025 19:28
Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com>
Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com>
Copilot AI changed the title [WIP] Add PATCH example to minimal API tutorial Add PATCH endpoint documentation to minimal API tutorial Nov 13, 2025
Copilot AI requested a review from wadepickett November 13, 2025 19:39
@wadepickett
Copy link
Contributor

@copilot there is inconsistent code block language specifiers: In min-web-api8.md, the .http code blocks in the Visual Studio tab lack the http language identifier (just instead ofhttp), while the main min-web-api.md correctly uses ```http. This should be consistent across files.

Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 13, 2026

@copilot there is inconsistent code block language specifiers: In min-web-api8.md, the .http code blocks in the Visual Studio tab lack the http language identifier (just instead ofhttp), while...

Fixed in commit 7fbde84. Updated the .http code blocks in min-web-api8.md to use ```http language identifier for consistency with min-web-api.md.

@wadepickett
Copy link
Contributor

@copilot, there is a technical bug which undermines the tutorial's correctness for teaching PATCH semantics:

The PATCH implementation has incorrect partial update logic for IsComplete: The Todo class has IsComplete as a non-nullable bool.

When a client sends only {"name": "run errands"}, the deserialized inputTodo.IsComplete defaults to false, which will incorrectly overwrite true values. The comparison inputTodo.IsComplete != todo.IsComplete doesn't differentiate between "field not provided" and "field explicitly set to false."

The analysis in the original issue this PR was to fix, recommended using a nullable bool? IsComplete DTO specifically to address this.

Fix this problem for all versions from version 7 and above where needed.

…ect partial update semantics

Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 13, 2026

@copilot, there is a technical bug which undermines the tutorial's correctness for teaching PATCH semantics:

The PATCH implementation has incorrect partial update logic for IsComplete: The Todo cla...

Fixed in commit dbf273d. Created TodoPatchDto class with nullable properties (bool? IsComplete) for all versions (7.x, 8.x, 9.x). The PATCH endpoint now correctly distinguishes between fields not provided (null) versus fields explicitly set to false, preventing incorrect overwrites. Updated documentation to explain the DTO pattern and why nullable properties are needed for proper partial update semantics.

@wadepickett
Copy link
Contributor

@copilot,

The current PR has a compile-error problem for readers following the tutorial step-by-step. In the "Add the API code" section, readers are told to paste the full Program.cs (via snippet_all), which includes a MapPatch call referencing TodoPatchDto. But the tutorial never tells readers to create the TodoPatchDto.cs file before that point, so the code won't compile.

Solution: Separate snippet_all from the PATCH code using #define blocks

The Program.cs sample files already use #define blocks (MINIMAL, FINAL, TYPEDR) to represent different stages of the tutorial. We will use this same pattern to separate the initial code (without PATCH) from the PATCH code.

Apply the following changes to all three version-specific files AND their corresponding tutorial markdown files.


Part 1: Modify Program.cs sample files

Apply the same structural change to all three files:

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

What to change in each Program.cs

  1. Remove the snippet_patch block from inside the FINAL / snippet_all section. The snippet_all block should contain only: GET, POST, PUT, and DELETE endpoints (no PATCH). This means removing the lines between // <snippet_patch> and // </snippet_patch> (inclusive) from within the #elif FINAL block.

  2. Add a new #elif WITHPATCH block after the #elif FINAL block's closing // </snippet_all> line but before the #elif TYPEDR block. This new block should contain:

    • The complete program with ALL endpoints including PATCH (GET, POST, PUT, PATCH, DELETE)
    • Keep the // <snippet_patch> and // </snippet_patch> markers around the MapPatch code so the tutorial article can reference it with ?name=snippet_patch
  3. Update the #define comment on line 1 to include the new define name. Change:

    #define FINAL // MINIMAL FINAL TYPEDR

    to:

    #define FINAL // MINIMAL FINAL WITHPATCH TYPEDR

    Keep #define FINAL as the active define (the FINAL block is the one used for snippet_all which is referenced first in the tutorial).

Example structure for each Program.cs after changes

#define FINAL // MINIMAL FINAL WITHPATCH TYPEDR
#if MINIMAL
// <snippet_min>
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

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

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

// <snippet_get>
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());

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

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

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

// <snippet_put>
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_put>

// <snippet_delete>
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();
});
// </snippet_delete>

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
// ... (keep existing TYPEDR block unchanged)
#endif

IMPORTANT: Keep the existing #elif TYPEDR block and everything after it exactly as it is. Do not modify it.


Part 2: Update the tutorial markdown files to add a "create TodoPatchDto.cs" step

Apply equivalent changes to all three tutorial markdown files:

  • aspnetcore/tutorials/min-web-api.md (9.x+ moniker, lines 518-529 area)
  • aspnetcore/tutorials/min-web-api/includes/min-web-api8.md (8.x moniker, lines 495-506 area)
  • aspnetcore/tutorials/min-web-api/includes/min-web-api6-7.md (7.x moniker section only — NOT the 6.x moniker section, lines 324-335 area)

What to change in each markdown file

In the "Examine the PATCH endpoint" section, before the existing line that says:

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

Add the following new paragraph instructing readers to create the TodoPatchDto.cs file. Use the correct version path for each file (9.x, 8.x, or 7.x):

For the 9.x file (min-web-api.md), add:

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`:

For the 8.x file (min-web-api8.md), add:

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`:

For the 7.x file (min-web-api6-7.md), add (only in the aspnetcore-7.0 moniker section, NOT the aspnetcore-6.0 section):

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`:

Part 3: Update the snippet reference for PATCH in the markdown files

In each of the three markdown files listed in Part 2, the "Examine the PATCH endpoint" section currently references the PATCH snippet like this (example for 9.x):

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

This reference is still correct because the snippet_patch markers exist in the new WITHPATCH block. The docfx build system will find the snippet markers regardless of which #define block they are in. No change is needed to the snippet reference lines.


Part 4: Do NOT change the aspnetcore-6.0 moniker section

The file aspnetcore/tutorials/min-web-api/includes/min-web-api6-7.md contains TWO moniker sections:

  • :::moniker range="= aspnetcore-7.0" (lines 1-490) — PATCH changes go here
  • :::moniker range="= aspnetcore-6.0" (lines 492-880) — do NOT add any PATCH content here

MapPatch was introduced in .NET 7. The .NET 6.0 section must remain unchanged with no PATCH references.


Summary of all files to modify

File Change
aspnetcore/tutorials/min-web-api/samples/9.x/todo/Program.cs Remove snippet_patch from FINAL block; add WITHPATCH block
aspnetcore/tutorials/min-web-api/samples/8.x/todo/Program.cs Remove snippet_patch from FINAL block; add WITHPATCH block
aspnetcore/tutorials/min-web-api/samples/7.x/todo/Program.cs Remove snippet_patch from FINAL block; add WITHPATCH block
aspnetcore/tutorials/min-web-api.md Add "create TodoPatchDto.cs" step before PATCH examine section
aspnetcore/tutorials/min-web-api/includes/min-web-api8.md Add "create TodoPatchDto.cs" step before PATCH examine section
aspnetcore/tutorials/min-web-api/includes/min-web-api6-7.md Add "create TodoPatchDto.cs" step in 7.0 moniker section only

Do NOT modify any other files. Do NOT modify the TodoPatchDto.cs files. Do NOT modify the .gitignore file. Do NOT modify any _SwaggerVersion or todoGroup or todoTypedResults or todoDTO sample folders.

…tion 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>
Copy link
Contributor Author

Copilot AI commented Feb 13, 2026

@copilot,

The current PR has a compile-error problem for readers following the tutorial step-by-step. In the "Add the API code" section, readers are told to paste the full Program.cs (via `snipp...

Fixed in commit b78cb43.

Changes made:

  1. Program.cs files (7.x, 8.x, 9.x): Removed snippet_patch from FINAL block, added new WITHPATCH block with complete code including PATCH endpoint, updated #define to include WITHPATCH
  2. Markdown files: Added instructions to create TodoPatchDto.cs before the PATCH section in all three files (min-web-api.md, min-web-api8.md, and min-web-api6-7.md for 7.0 moniker only)

The FINAL block (referenced by snippet_all) now contains only GET, POST, PUT, and DELETE endpoints without PATCH. The PATCH endpoint is in the separate WITHPATCH block with snippet_patch markers. Readers will now create TodoPatchDto.cs before seeing the PATCH code, preventing compile errors.

Copilot AI requested a review from wadepickett February 13, 2026 02:47
@wadepickett wadepickett marked this pull request as ready for review February 22, 2026 22:35
Copy link
Contributor

@wadepickett wadepickett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants