From 7463a153bacad52f9bada5f460464bc6826bbdc3 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Tue, 3 Jun 2025 19:07:53 -0700 Subject: [PATCH 01/20] JsonPatch System.Text.Json Update .NET 10 Preview 4 --- aspnetcore/web-api/jsonpatch.md | 454 +++++++++++++++++++++----------- 1 file changed, 306 insertions(+), 148 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index f2a883caefea..13fae21e794a 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -5,7 +5,7 @@ description: Learn how to handle JSON Patch requests in an ASP.NET Core web API. monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 03/09/2022 +ms.date: 05/22/2025 uid: web-api/jsonpatch --- # JsonPatch in ASP.NET Core web API @@ -14,237 +14,395 @@ uid: web-api/jsonpatch This article explains how to handle JSON Patch requests in an ASP.NET Core web API. -## Package installation +**[JSON Patch](https://jsonpatch.com/)**: -JSON Patch support in ASP.NET Core web API is based on `Newtonsoft.Json` and requires the [`Microsoft.AspNetCore.Mvc.NewtonsoftJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/) NuGet package. To enable JSON Patch support: +* Is a standard format for describing changes to apply to a JSON document. +* Is defined in [RFC 6902] and is widely used in RESTful APIs to perform partial updates to JSON resources. +* Describes a sequence of operations such as `add`, `remove`, `replace`, `move`, `copy`, and `test` that modify a JSON document. -* Install the [`Microsoft.AspNetCore.Mvc.NewtonsoftJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/) NuGet package. -* Call . For example: +In web apps, JSON Patch is commonly used in a PATCH operation to perform partial updates of a resource. Rather than sending the entire resource for an update, clients can send a JSON Patch document containing only the changes. Patching reduces payload size and improves efficiency. - :::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/Program.cs" id="snippet1" highlight="4"::: +JSON Patch support in ASP.NET Core web API is based on `System.Text.Json` serialization, starting with .NET 10. This release introduces a new implementation of `JsonPatch` based on `System.Text.Json` serialization. This feature: -`AddNewtonsoftJson` replaces the default `System.Text.Json`-based input and output formatters used for formatting ***all*** JSON content. This extension method is compatible with the following MVC service registration methods: +* Aligns with modern .NET practices by leveraging the `System.Text.Json` library, which is optimized for .NET. +* Provides improved performance and reduced memory usage compared to the legacy `Newtonsoft.Json`-based implementation. For more information on the legacy `Newtonsoft.Json`-based implementation, see the [.NET 9 version of this article](~/web-api/jsonpatch?view=aspnetcore-9.0). -* -* -* +The following benchmarks compare the performance of the new `System.Text.Json` implementation with the legacy `Newtonsoft.Json` implementation: -JsonPatch requires setting the `Content-Type` header to `application/json-patch+json`. +| Scenario | Implementation | Mean | Allocated Memory | +|----------------------------|------------------------|------------|------------------| +| **Application Benchmarks** | Newtonsoft.JsonPatch | 271.924 µs | 25 KB | +| | System.Text.JsonPatch | 1.584 µs | 3 KB | +| **Deserialization Benchmarks** | Newtonsoft.JsonPatch | 19.261 µs | 43 KB | +| | System.Text.JsonPatch | 7.917 µs | 7 KB | -## Add support for JSON Patch when using System.Text.Json +These benchmarks highlight significant performance gains and reduced memory usage with the new implementation. -The `System.Text.Json`-based input formatter doesn't support JSON Patch. To add support for JSON Patch using `Newtonsoft.Json`, while leaving the other input and output formatters unchanged: +> [!NOTE] +> The new implementation of `JsonPatch` based on `System.Text.Json` serialization isn't a drop-in replacement for the legacy `Newtonsoft.Json`-based implementation. It doesn't support dynamic types, for example [`ExpandoObject`](/dotnet/api/system.dynamic.expandoobject). -* Install the [`Microsoft.AspNetCore.Mvc.NewtonsoftJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/) NuGet package. -* Update `Program.cs`: +> [!IMPORTANT] +> The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, the new implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. - :::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/Program.cs" id="snippet_both" highlight="6-9"::: - :::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/MyJPIF.cs"::: +## Enable JSON Patch support with `System.Text.Json` -The preceding code creates an instance of and inserts it as the first entry in the collection. This order of registration ensures that: +To enable JSON Patch support with `System.Text.Json`, install the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch/) NuGet package. -* `NewtonsoftJsonPatchInputFormatter` processes JSON Patch requests. -* The existing `System.Text.Json`-based input and formatters process all other JSON requests and responses. +```dotnetcli +dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease +``` -Use the `Newtonsoft.Json.JsonConvert.SerializeObject` method to serialize a . +This package provides a `JsonPatchDocument` class to represent a JSON Patch document for objects of type `T` and custom logic for serializing and deserializing JSON Patch documents using `System.Text.Json`. The key method of the `JsonPatchDocument` class is `ApplyTo`, which applies the patch operations to a target object of type `T`. -## PATCH HTTP request method +## JSON Patch Operations -The PUT and [PATCH](https://tools.ietf.org/html/rfc5789) methods are used to update an existing resource. The difference between them is that PUT replaces the entire resource, while PATCH specifies only the changes. +The following sections describe the supported JSON Patch operations `add`, `remove`, `replace`, `move`, `copy`, and `test` that modify a JSON document and provide examples of their usage. -## JSON Patch +### The `add` Operation -[JSON Patch](https://tools.ietf.org/html/rfc6902) is a format for specifying updates to be applied to a resource. A JSON Patch document has an array of *operations*. Each operation identifies a particular type of change. Examples of such changes include adding an array element or replacing a property value. +* If `path` points to an array element: Inserts a new element before the one specified by `path`. +* If `path` points to a property: Sets the property value. +* If `path` points to a nonexistent location: + * If the resource to patch is a static object: The request fails. -For example, the following JSON documents represent a resource, a JSON Patch document for the resource, and the result of applying the Patch operations. +Example: -### Resource example +```json +[ + { + "op": "add", + "path": "/customerName", + "value": "Barry" + }, + { + "op": "add", + "path": "/orders/-", + "value": { + "orderName": "Order2", + "orderType": null + } + } +] +``` -:::code language="json" source="~/web-api/jsonpatch/snippets/customer.json"::: +### The `remove` Operation -### JSON patch example +* If `path` points to an array element**: Removes the element. +* If `path` points to a property**: +* If the resource to patch is a static object: + * If the property is nullable: Sets it to `null`. + * If the property is non-nullable: Sets it to `default`. -:::code language="json" source="~/web-api/jsonpatch/snippets/add.json"::: +Example: -In the preceding JSON: +```json +[ + { + "op": "remove", + "path": "/customerName" + }, + { + "op": "remove", + "path": "/orders/0" + } +] +``` -* The `op` property indicates the type of operation. -* The `path` property indicates the element to update. -* The `value` property provides the new value. +### The `replace` Operation -### Resource after patch +This operation is functionally the same as a `remove` followed by an `add`. -Here's the resource after applying the preceding JSON Patch document: +Example: ```json -{ - "customerName": "Barry", - "orders": [ - { - "orderName": "Order0", - "orderType": null - }, - { - "orderName": "Order1", - "orderType": null - }, - { +[ + { + "op": "replace", + "path": "/customerName", + "value": "Barry" + }, + { + "op": "replace", + "path": "/orders/0", + "value": { "orderName": "Order2", "orderType": null } - ] -} + } +] ``` -The changes made by applying a JSON Patch document to a resource are atomic. If any operation in the list fails, no operation in the list is applied. - -## Path syntax - -The [path](https://tools.ietf.org/html/rfc6901) property of an operation object has slashes between levels. For example, `"/address/zipCode"`. +### The `move` Operation -Zero-based indexes are used to specify array elements. The first element of the `addresses` array would be at `/addresses/0`. To `add` to the end of an array, use a hyphen (`-`) rather than an index number: `/addresses/-`. +* If `path` points to an array element**: Copies from the element at `from` to the location specified by `path`, then removes the element at `from`. +* If `path` points to a property**: Copies the value of the `from` property to the `path` property, then removes the `from` property. +* If `path` points to a nonexistent property**: + * If the resource to patch is a static object: The request fails. -### Operations +Example: -The following table shows supported operations as defined in the [JSON Patch specification](https://tools.ietf.org/html/rfc6902): - -|Operation | Notes | -|-----------|--------------------------------| -| `add` | Add a property or array element. For existing property: set value.| -| `remove` | Remove a property or array element. | -| `replace` | Same as `remove` followed by `add` at same location. | -| `move` | Same as `remove` from source followed by `add` to destination using value from source. | -| `copy` | Same as `add` to destination using value from source. | -| `test` | Return success status code if value at `path` = provided `value`.| +```json +[ + { + "op": "move", + "from": "/orders/0/orderName", + "path": "/customerName" + }, + { + "op": "move", + "from": "/orders/1", + "path": "/orders/0" + } +] +``` -## JSON Patch in ASP.NET Core +### The `copy` Operation -The ASP.NET Core implementation of JSON Patch is provided in the [Microsoft.AspNetCore.JsonPatch](https://www.nuget.org/packages/microsoft.aspnetcore.jsonpatch/) NuGet package. +This operation is functionally the same as a `move` operation without the final `remove` step. -## Action method code +Example: -In an API controller, an action method for JSON Patch: +```json +[ + { + "op": "copy", + "from": "/orders/0/orderName", + "path": "/customerName" + }, + { + "op": "copy", + "from": "/orders/1", + "path": "/orders/0" + } +] +``` -* Is annotated with the `HttpPatch` attribute. -* Accepts a , typically with [`[FromBody]`](xref:Microsoft.AspNetCore.Mvc.FromBodyAttribute). -* Calls on the patch document to apply the changes. +### The `test` Operation -Here's an example: +* Behavior**: If the value at the location indicated by `path` is different from the value provided in `value`, the request fails. In that case, the entire PATCH request fails, even if all other operations in the patch document would otherwise succeed. -:::code language="csharp" source="~/web-api/jsonpatch/samples/3.x/api/Controllers/HomeController.cs" id="snippet_PatchAction" highlight="1,3,9"::: +Example: -This code from the sample app works with the following `Customer` model: +```json +[ + { + "op": "test", + "path": "/customerName", + "value": "Nancy" + }, + { + "op": "add", + "path": "/customerName", + "value": "Barry" + } +] +``` -:::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/Models/Customer.cs"::: +## Apply a JSON Patch document to an object -:::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/Models/Order.cs"::: +The following examples demonstrate how to use the `ApplyTo` method to apply a JSON Patch document to an object. -The sample action method: +## Example: Apply a `JsonPatchDocument` to an object -* Constructs a `Customer`. -* Applies the patch. -* Returns the result in the body of the response. +The following example demonstrates: -In a real app, the code would retrieve the data from a store such as a database and update the database after applying the patch. +1. The `add`, `replace`, and `remove` operations. +1. Operations on nested properties. +1. Adding a new item to an array. +1. Using a JSON String Enum Converter in a JSON patch document. -### Model state +```csharp +// Original object +var person = new Person { + FirstName = "John", + LastName = "Doe", + Email = "johndoe@gmail.com", + PhoneNumbers = [new() {Number = "123-456-7890", Type = PhoneNumberType.Mobile}], + Address = new Address + { + Street = "123 Main St", + City = "Anytown", + State = "TX" + } +}; + +// Raw JSON patch document +string jsonPatch = """ +[ + { "op": "replace", "path": "/FirstName", "value": "Jane" }, + { "op": "remove", "path": "/Email"}, + { "op": "add", "path": "/Address/ZipCode", "value": "90210" }, + { "op": "add", "path": "/PhoneNumbers/-", "value": { "Number": "987-654-3210", + "Type": "Work" } } +] +"""; + +// Deserialize the JSON patch document +var patchDoc = JsonSerializer.Deserialize>(jsonPatch); + +// Apply the JSON patch document +patchDoc!.ApplyTo(person); + +// Output updated object +Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions)); +``` -The preceding action method example calls an overload of `ApplyTo` that takes model state as one of its parameters. With this option, you can get error messages in responses. The following example shows the body of a 400 Bad Request response for a `test` operation: +The previous example results in the following output of the updated object: -```json +```output { - "Customer": [ - "The current value 'John' at path 'customerName' != test value 'Nancy'." - ] + "firstName": "Jane", + "lastName": "Doe", + "address": { + "street": "123 Main St", + "city": "Anytown", + "state": "TX", + "zipCode": "90210" + }, + "phoneNumbers": [ + { + "number": "123-456-7890", + "type": "Mobile" + }, + { + "number": "987-654-3210", + "type": "Work" + } + ] } ``` -### Dynamic objects - -The following action method example shows how to apply a patch to a dynamic object: +The `ApplyTo` method generally follows the conventions and options of `System.Text.Json` for processing the `JsonPatchDocument`, including the behavior controlled by the following options: -:::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/Controllers/HomeController.cs" id="snippet_Dynamic"::: +* `NumberHandling`: Whether numeric properties can be read from strings. +* `PropertyNameCaseInsensitive`: Whether property names are case-sensitive. -## The add operation +Key differences between `System.Text.Json` and the new `JsonPatchDocument` implementation: -* If `path` points to an array element: inserts new element before the one specified by `path`. -* If `path` points to a property: sets the property value. -* If `path` points to a nonexistent location: - * If the resource to patch is a dynamic object: adds a property. - * If the resource to patch is a static object: the request fails. - -The following sample patch document sets the value of `CustomerName` and adds an `Order` object to the end of the `Orders` array. +* The runtime type of the target object, not the declared type, determines which properties `ApplyTo` patches. +* `System.Text.Json` deserialization relies on the declared type to identify eligible properties. -:::code language="json" source="~/web-api/jsonpatch/snippets/add.json"::: +## Example: Applying a JsonPatchDocument with error handling -## The remove operation +There are various errors that can occur when applying a JSON Patch document. For example, the target object may not have the specified property, or the value specified might be incompatible with the property type. -* If `path` points to an array element: removes the element. -* If `path` points to a property: - * If resource to patch is a dynamic object: removes the property. - * If resource to patch is a static object: - * If the property is nullable: sets it to null. - * If the property is non-nullable, sets it to `default`. +JSON `Patch` supports the `test` operation, which checks if a specified value equals the target property. If it doesn't, it returns an error. -The following sample patch document sets `CustomerName` to null and deletes `Orders[0]`: +The following example demonstrates how to handle these errors gracefully. -:::code language="json" source="~/web-api/jsonpatch/snippets/remove.json"::: +> [!Important] +> The object passed to the `ApplyTo` method is modified in place. The caller is responsible for discarding changes if any operation fails. -## The replace operation - -This operation is functionally the same as a `remove` followed by an `add`. +```csharp +// Original object +var person = new Person { + FirstName = "John", + LastName = "Doe", + Email = "johndoe@gmail.com" +}; -The following sample patch document sets the value of `CustomerName` and replaces `Orders[0]`with a new `Order` object: +// Raw JSON patch document +string jsonPatch = """ +[ + { "op": "replace", "path": "/Email", "value": "janedoe@gmail.com"}, + { "op": "test", "path": "/FirstName", "value": "Jane" }, + { "op": "replace", "path": "/LastName", "value": "Smith" } +] +"""; -:::code language="json" source="~/web-api/jsonpatch/snippets/replace.json"::: +// Deserialize the JSON patch document +var patchDoc = JsonSerializer.Deserialize>(jsonPatch); -## The move operation - -* If `path` points to an array element: copies `from` element to location of `path` element, then runs a `remove` operation on the `from` element. -* If `path` points to a property: copies value of `from` property to `path` property, then runs a `remove` operation on the `from` property. -* If `path` points to a nonexistent property: - * If the resource to patch is a static object: the request fails. - * If the resource to patch is a dynamic object: copies `from` property to location indicated by `path`, then runs a `remove` operation on the `from` property. - -The following sample patch document: +// Apply the JSON patch document, catching any errors +Dictionary? errors = null; +patchDoc!.ApplyTo(person, jsonPatchError => + { + errors ??= new (); + var key = jsonPatchError.AffectedObject.GetType().Name; + if (!errors.ContainsKey(key)) + { + errors.Add(key, new string[] { }); + } + errors[key] = errors[key].Append(jsonPatchError.ErrorMessage).ToArray(); + }); +if (errors != null) +{ + // Print the errors + foreach (var error in errors) + { + Console.WriteLine($"Error in {error.Key}: {string.Join(", ", error.Value)}"); + } +} -* Copies the value of `Orders[0].OrderName` to `CustomerName`. -* Sets `Orders[0].OrderName` to null. -* Moves `Orders[1]` to before `Orders[0]`. +// Output updated object +Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions)); +``` -:::code language="json" source="~/web-api/jsonpatch/snippets/move.json"::: +The previous example results in the following output: -## The copy operation +```output +Error in Person: The current value 'John' at path 'FirstName' is not equal +to the test value 'Jane'. +{ + "firstName": "John", + "lastName": "Smith", <<< Modified! + "email": "janedoe@gmail.com", <<< Modified! + "phoneNumbers": [] +} +``` -This operation is functionally the same as a `move` operation without the final `remove` step. +## Mitigating security risks -The following sample patch document: +When using the `Microsoft.AspNetCore.JsonPatch.SystemTextJson` package, it's critical to understand and mitigate potential security risks. The following sections outline the identified security risks associated with JSON Patch and provide recommended mitigations to ensure secure usage of the package. -* Copies the value of `Orders[0].OrderName` to `CustomerName`. -* Inserts a copy of `Orders[1]` before `Orders[0]`. +> [!IMPORTANT] +> ***This is not an exhaustive list of threats.*** app developers must conduct their own threat model reviews to determine an app-specific comprehensive list and come up with appropriate mitigations as needed. For example, apps which expose collections to patch operations should consider the potential for algorithmic complexity attacks if those operations insert or remove elements at the beginning of the collection. -:::code language="json" source="~/web-api/jsonpatch/snippets/copy.json"::: +By running comprehensive threat models for their own apps and addressing identified threats while following the recommended mitigations below, consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks. -## The test operation +Consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks, including: -If the value at the location indicated by `path` is different from the value provided in `value`, the request fails. In that case, the whole PATCH request fails even if all other operations in the patch document would otherwise succeed. +* Run comprehensive threat models for their own apps. +* Address identified threats. +* Follow the recommended mitigations in the following sections. -The `test` operation is commonly used to prevent an update when there's a concurrency conflict. +### Denial of Service (DoS) via memory amplification -The following sample patch document has no effect if the initial value of `CustomerName` is "John", because the test fails: +* **Scenario**: A malicious client submits a `copy` operation that duplicates large object graphs multiple times, leading to excessive memory consumption. +* **Impact**: Potential Out-Of-Memory (OOM) conditions, causing service disruptions. +* **Mitigation**: + * Validate incoming JSON Patch documents for size and structure before calling `ApplyTo`. + * The validation needs to be app specific, but an example validation can look similar to the following: -:::code language="json" source="~/web-api/jsonpatch/snippets/test-fail.json"::: +```csharp +public void Validate(JsonPatchDocument patch) +{ + // This is just an example. It's up to the developer to make sure that + // this case is handled properly, based on the app needs. + if (patch.Operations.Where(op=>op.OperationType == OperationType.Copy).Count() + > MaxCopyOperationsCount) + { + throw new InvalidOperationException(); + } +} +``` -## Get the code +### Business Logic Subversion -[View or download sample code](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/web-api/jsonpatch/samples). ([How to download](xref:index#how-to-download-a-sample)). +* **Scenario**: Patch operations can manipulate fields with implicit invariants, (for example, internal flags, IDs, or computed fields), violating business constraints. +* **Impact**: Data integrity issues and unintended app behavior. +* **Mitigation**: + * Use POCO objects with explicitly defined properties that are safe to modify. + * Avoid exposing sensitive or security-critical properties in the target object. + * If no POCO object is used, validate the patched object after applying operations to ensure business rules and invariants aren't violated. -To test the sample, run the app and send HTTP requests with the following settings: +### Authentication and authorization -* URL: `http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate` -* HTTP method: `PATCH` -* Header: `Content-Type: application/json-patch+json` -* Body: Copy and paste one of the JSON patch document samples from the *JSON* project folder. +* **Scenario**: Unauthenticated or unauthorized clients send malicious JSON Patch requests. +* **Impact**: Unauthorized access to modify sensitive data or disrupt app behavior. +* **Mitigation**: + * Protect endpoints accepting JSON Patch requests with proper authentication and authorization mechanisms. + * Restrict access to trusted clients or users with appropriate permissions. ## Additional resources From fffc02595bc69b82d0cea29fb459966ad8af676d Mon Sep 17 00:00:00 2001 From: wadepickett Date: Tue, 3 Jun 2025 19:27:52 -0700 Subject: [PATCH 02/20] Fixed link, updated doc meta data --- aspnetcore/web-api/jsonpatch.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 13fae21e794a..e45113e57351 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -1,11 +1,11 @@ --- title: JsonPatch in ASP.NET Core web API -author: rick-anderson +author: wadepickett description: Learn how to handle JSON Patch requests in an ASP.NET Core web API. monikerRange: '>= aspnetcore-3.1' -ms.author: riande +ms.author: wpickett ms.custom: mvc -ms.date: 05/22/2025 +ms.date: 06/03/2025 uid: web-api/jsonpatch --- # JsonPatch in ASP.NET Core web API @@ -25,7 +25,7 @@ In web apps, JSON Patch is commonly used in a PATCH operation to perform partial JSON Patch support in ASP.NET Core web API is based on `System.Text.Json` serialization, starting with .NET 10. This release introduces a new implementation of `JsonPatch` based on `System.Text.Json` serialization. This feature: * Aligns with modern .NET practices by leveraging the `System.Text.Json` library, which is optimized for .NET. -* Provides improved performance and reduced memory usage compared to the legacy `Newtonsoft.Json`-based implementation. For more information on the legacy `Newtonsoft.Json`-based implementation, see the [.NET 9 version of this article](~/web-api/jsonpatch?view=aspnetcore-9.0). +* Provides improved performance and reduced memory usage compared to the legacy `Newtonsoft.Json`-based implementation. For more information on the legacy `Newtonsoft.Json`-based implementation, see the [.NET 9 version of this article](xref:/web-api/jsonpatch?view=aspnetcore-9.0&preserve-view=true). The following benchmarks compare the performance of the new `System.Text.Json` implementation with the legacy `Newtonsoft.Json` implementation: From 45f0a2ffa8a2a00718e0860b78d4e2702372ad66 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Tue, 3 Jun 2025 19:35:21 -0700 Subject: [PATCH 03/20] Fix link --- aspnetcore/web-api/jsonpatch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index e45113e57351..87e55e1539b9 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -25,7 +25,7 @@ In web apps, JSON Patch is commonly used in a PATCH operation to perform partial JSON Patch support in ASP.NET Core web API is based on `System.Text.Json` serialization, starting with .NET 10. This release introduces a new implementation of `JsonPatch` based on `System.Text.Json` serialization. This feature: * Aligns with modern .NET practices by leveraging the `System.Text.Json` library, which is optimized for .NET. -* Provides improved performance and reduced memory usage compared to the legacy `Newtonsoft.Json`-based implementation. For more information on the legacy `Newtonsoft.Json`-based implementation, see the [.NET 9 version of this article](xref:/web-api/jsonpatch?view=aspnetcore-9.0&preserve-view=true). +* Provides improved performance and reduced memory usage compared to the legacy `Newtonsoft.Json`-based implementation. For more information on the legacy `Newtonsoft.Json`-based implementation, see the [.NET 9 version of this article](xref:web-api/jsonpatch?view=aspnetcore-9.0&preserve-view=true). The following benchmarks compare the performance of the new `System.Text.Json` implementation with the legacy `Newtonsoft.Json` implementation: From 3ac895763cfcc0ab22ad5641430fa4244e1ac091 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Tue, 3 Jun 2025 20:04:07 -0700 Subject: [PATCH 04/20] Added Mitigating security risks section to version 9 of article --- aspnetcore/web-api/jsonpatch.md | 10 ++-- .../web-api/jsonpatch/includes/jsonpatch9.md | 54 ++++++++++++++++++- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 87e55e1539b9..c1ca7f90cf46 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -206,10 +206,10 @@ The following examples demonstrate how to use the `ApplyTo` method to apply a JS The following example demonstrates: -1. The `add`, `replace`, and `remove` operations. -1. Operations on nested properties. -1. Adding a new item to an array. -1. Using a JSON String Enum Converter in a JSON patch document. +* The `add`, `replace`, and `remove` operations. +* Operations on nested properties. +* Adding a new item to an array. +* Using a JSON String Enum Converter in a JSON patch document. ```csharp // Original object @@ -282,7 +282,7 @@ Key differences between `System.Text.Json` and the new `JsonPatchDocument` im * The runtime type of the target object, not the declared type, determines which properties `ApplyTo` patches. * `System.Text.Json` deserialization relies on the declared type to identify eligible properties. -## Example: Applying a JsonPatchDocument with error handling +## Example: Apply a JsonPatchDocument with error handling There are various errors that can occur when applying a JSON Patch document. For example, the target object may not have the specified property, or the value specified might be incompatible with the property type. diff --git a/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md b/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md index b002fc0fdee2..2658ce3d718e 100644 --- a/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md +++ b/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md @@ -4,7 +4,12 @@ This article explains how to handle JSON Patch requests in an ASP.NET Core web A ## Package installation -JSON Patch support in ASP.NET Core web API is based on `Newtonsoft.Json` and requires the [`Microsoft.AspNetCore.Mvc.NewtonsoftJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/) NuGet package. To enable JSON Patch support: +JSON Patch support in ASP.NET Core web API is based on `Newtonsoft.Json` and requires the [`Microsoft.AspNetCore.Mvc.NewtonsoftJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/) NuGet package. + +> [!IMPORTANT] +> The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, this implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. + +To enable JSON Patch support: * Install the [`Microsoft.AspNetCore.Mvc.NewtonsoftJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/) NuGet package. * Call . For example: @@ -476,6 +481,53 @@ To test the sample, run the app and send HTTP requests with the following settin * Header: `Content-Type: application/json-patch+json` * Body: Copy and paste one of the JSON patch document samples from the *JSON* project folder. +## Mitigating security risks + +When using the `Microsoft.AspNetCore.JsonPatch` package with the `Newtonsoft.Json`-based implementation, it's critical to understand and mitigate potential security risks. The following sections outline the identified security risks associated with JSON Patch and provide recommended mitigations to ensure secure usage of the package. + +> [!IMPORTANT] +> ***This is not an exhaustive list of threats.*** App developers must conduct their own threat model reviews to determine an app-specific comprehensive list and come up with appropriate mitigations as needed. For example, apps which expose collections to patch operations should consider the potential for algorithmic complexity attacks if those operations insert or remove elements at the beginning of the collection. + +By running comprehensive threat models for their own apps and addressing identified threats while following the recommended mitigations below, consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks. + +### Denial of Service (DoS) via memory amplification + +* **Scenario**: A malicious client submits a `copy` operation that duplicates large object graphs multiple times, leading to excessive memory consumption. +* **Impact**: Potential Out-Of-Memory (OOM) conditions, causing service disruptions. +* **Mitigation**: + * Validate incoming JSON Patch documents for size and structure before calling `ApplyTo`. + * The validation needs to be app specific, but an example validation can look similar to the following: + +```csharp +public void Validate(JsonPatchDocument patch) +{ + // This is just an example. It's up to the developer to make sure that + // this case is handled properly, based on the app needs. + if (patch.Operations.Where(op => op.OperationType == OperationType.Copy).Count() + > MaxCopyOperationsCount) + { + throw new InvalidOperationException(); + } +} +``` + +### Business Logic Subversion + +* **Scenario**: Patch operations can manipulate fields with implicit invariants (for example, internal flags, IDs, or computed fields), violating business constraints. +* **Impact**: Data integrity issues and unintended app behavior. +* **Mitigation**: + * Use POCO objects with explicitly defined properties that are safe to modify. + * Avoid exposing sensitive or security-critical properties in the target object. + * If no POCO object is used, validate the patched object after applying operations to ensure business rules and invariants aren't violated. + +### Authentication and authorization + +* **Scenario**: Unauthenticated or unauthorized clients send malicious JSON Patch requests. +* **Impact**: Unauthorized access to modify sensitive data or disrupt app behavior. +* **Mitigation**: + * Protect endpoints accepting JSON Patch requests with proper authentication and authorization mechanisms. + * Restrict access to trusted clients or users with appropriate permissions. + ## Additional resources * [IETF RFC 5789 PATCH method specification](https://tools.ietf.org/html/rfc5789) From 2637fb4510d60df4888e5ffc2984cf2e4361c5aa Mon Sep 17 00:00:00 2001 From: wadepickett Date: Tue, 3 Jun 2025 20:22:58 -0700 Subject: [PATCH 05/20] Added mitigating security section to all previous versions of article --- .../web-api/jsonpatch/includes/jsonpatch9.md | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md b/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md index 2658ce3d718e..5876658fac53 100644 --- a/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md +++ b/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md @@ -2,13 +2,13 @@ This article explains how to handle JSON Patch requests in an ASP.NET Core web API. +> [!IMPORTANT] +> The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, this implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. + ## Package installation JSON Patch support in ASP.NET Core web API is based on `Newtonsoft.Json` and requires the [`Microsoft.AspNetCore.Mvc.NewtonsoftJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/) NuGet package. -> [!IMPORTANT] -> The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, this implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. - To enable JSON Patch support: * Install the [`Microsoft.AspNetCore.Mvc.NewtonsoftJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/) NuGet package. @@ -239,6 +239,53 @@ To test the sample, run the app and send HTTP requests with the following settin * Header: `Content-Type: application/json-patch+json` * Body: Copy and paste one of the JSON patch document samples from the *JSON* project folder. +## Mitigating security risks + +When using the `Microsoft.AspNetCore.JsonPatch` package with the `Newtonsoft.Json`-based implementation, it's critical to understand and mitigate potential security risks. The following sections outline the identified security risks associated with JSON Patch and provide recommended mitigations to ensure secure usage of the package. + +> [!IMPORTANT] +> ***This is not an exhaustive list of threats.*** App developers must conduct their own threat model reviews to determine an app-specific comprehensive list and come up with appropriate mitigations as needed. For example, apps which expose collections to patch operations should consider the potential for algorithmic complexity attacks if those operations insert or remove elements at the beginning of the collection. + +By running comprehensive threat models for their own apps and addressing identified threats while following the recommended mitigations below, consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks. + +### Denial of Service (DoS) via memory amplification + +* **Scenario**: A malicious client submits a `copy` operation that duplicates large object graphs multiple times, leading to excessive memory consumption. +* **Impact**: Potential Out-Of-Memory (OOM) conditions, causing service disruptions. +* **Mitigation**: + * Validate incoming JSON Patch documents for size and structure before calling `ApplyTo`. + * The validation needs to be app specific, but an example validation can look similar to the following: + +```csharp +public void Validate(JsonPatchDocument patch) +{ + // This is just an example. It's up to the developer to make sure that + // this case is handled properly, based on the app needs. + if (patch.Operations.Where(op => op.OperationType == OperationType.Copy).Count() + > MaxCopyOperationsCount) + { + throw new InvalidOperationException(); + } +} +``` + +### Business Logic Subversion + +* **Scenario**: Patch operations can manipulate fields with implicit invariants (for example, internal flags, IDs, or computed fields), violating business constraints. +* **Impact**: Data integrity issues and unintended app behavior. +* **Mitigation**: + * Use POCO objects with explicitly defined properties that are safe to modify. + * Avoid exposing sensitive or security-critical properties in the target object. + * If no POCO object is used, validate the patched object after applying operations to ensure business rules and invariants aren't violated. + +### Authentication and authorization + +* **Scenario**: Unauthenticated or unauthorized clients send malicious JSON Patch requests. +* **Impact**: Unauthorized access to modify sensitive data or disrupt app behavior. +* **Mitigation**: + * Protect endpoints accepting JSON Patch requests with proper authentication and authorization mechanisms. + * Restrict access to trusted clients or users with appropriate permissions. + ## Additional resources * [IETF RFC 5789 PATCH method specification](https://tools.ietf.org/html/rfc5789) @@ -252,6 +299,9 @@ To test the sample, run the app and send HTTP requests with the following settin This article explains how to handle JSON Patch requests in an ASP.NET Core web API. +> [!IMPORTANT] +> The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, this implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. + ## Package installation To enable JSON Patch support in your app, complete the following steps: From 4a9dc0466fe190af5f2f85948fbb5b38c61f5e7b Mon Sep 17 00:00:00 2001 From: wadepickett Date: Tue, 3 Jun 2025 20:36:43 -0700 Subject: [PATCH 06/20] Removed bold on test behavior --- aspnetcore/web-api/jsonpatch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index c1ca7f90cf46..038ff8bd7380 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -179,7 +179,7 @@ Example: ### The `test` Operation -* Behavior**: If the value at the location indicated by `path` is different from the value provided in `value`, the request fails. In that case, the entire PATCH request fails, even if all other operations in the patch document would otherwise succeed. +* If the value at the location indicated by `path` is different from the value provided in `value`, the request fails. In that case, the entire PATCH request fails, even if all other operations in the patch document would otherwise succeed. Example: From 175a38d7509dffa3cf99889ba4a165c3c35a12c0 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Wed, 4 Jun 2025 16:54:11 -0700 Subject: [PATCH 07/20] Moved Action Method code section back in --- aspnetcore/web-api/jsonpatch.md | 54 +++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 038ff8bd7380..9f717b32a584 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -54,6 +54,45 @@ dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease This package provides a `JsonPatchDocument` class to represent a JSON Patch document for objects of type `T` and custom logic for serializing and deserializing JSON Patch documents using `System.Text.Json`. The key method of the `JsonPatchDocument` class is `ApplyTo`, which applies the patch operations to a target object of type `T`. +## Action method code + +In an API controller, an action method for JSON Patch: + +* Is annotated with the `HttpPatch` attribute. +* Accepts a , typically with [`[FromBody]`](xref:Microsoft.AspNetCore.Mvc.FromBodyAttribute). +* Calls on the patch document to apply the changes. + +Here's an example: + +:::code language="csharp" source="~/web-api/jsonpatch/samples/3.x/api/Controllers/HomeController.cs" id="snippet_PatchAction" highlight="1,3,9"::: + +This code from the sample app works with the following `Customer` model: + +:::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/Models/Customer.cs"::: + +:::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/Models/Order.cs"::: + +The sample action method: + +* Constructs a `Customer`. +* Applies the patch. +* Returns the result in the body of the response. + +> [!NOTE] +> In a real app, the data would typically be retrieved from a store such as a database. After applying the patch, the updated data would be saved back to the database. + +### Model state + +The preceding action method example calls an overload of `ApplyTo` that takes model state as one of its parameters. With this option, you can get error messages in responses. The following example shows the body of a 400 Bad Request response for a `test` operation: + +```json +{ + "Customer": [ + "The current value 'John' at path 'customerName' != test value 'Nancy'." + ] +} +``` + ## JSON Patch Operations The following sections describe the supported JSON Patch operations `add`, `remove`, `replace`, `move`, `copy`, and `test` that modify a JSON document and provide examples of their usage. @@ -202,7 +241,7 @@ Example: The following examples demonstrate how to use the `ApplyTo` method to apply a JSON Patch document to an object. -## Example: Apply a `JsonPatchDocument` to an object +### Example: Apply a `JsonPatchDocument` to an object The following example demonstrates: @@ -282,7 +321,7 @@ Key differences between `System.Text.Json` and the new `JsonPatchDocument` im * The runtime type of the target object, not the declared type, determines which properties `ApplyTo` patches. * `System.Text.Json` deserialization relies on the declared type to identify eligible properties. -## Example: Apply a JsonPatchDocument with error handling +### Example: Apply a JsonPatchDocument with error handling There are various errors that can occur when applying a JSON Patch document. For example, the target object may not have the specified property, or the value specified might be incompatible with the property type. @@ -404,6 +443,17 @@ public void Validate(JsonPatchDocument patch) * Protect endpoints accepting JSON Patch requests with proper authentication and authorization mechanisms. * Restrict access to trusted clients or users with appropriate permissions. +## Get the code + +[View or download sample code](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/web-api/jsonpatch/samples). ([How to download](xref:index#how-to-download-a-sample)). + +To test the sample, run the app and send HTTP requests with the following settings: + +* URL: `http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate` +* HTTP method: `PATCH` +* Header: `Content-Type: application/json-patch+json` +* Body: Copy and paste one of the JSON patch document samples from the *JSON* project folder. + ## Additional resources * [IETF RFC 5789 PATCH method specification](https://tools.ietf.org/html/rfc5789) From 9d7a34f5cbe38b6dadb97fad62e8db1b3a3cd194 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Wed, 11 Jun 2025 13:31:37 -0700 Subject: [PATCH 08/20] Corrected NuGet link, and mentioned the package link again at top --- aspnetcore/web-api/jsonpatch.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 9f717b32a584..20a3f47b6f9c 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -14,6 +14,8 @@ uid: web-api/jsonpatch This article explains how to handle JSON Patch requests in an ASP.NET Core web API. +JSON Patch support in ASP.NET Core web API is based on `System.Text.Json` serialization, and requires the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson) NuGet package. + **[JSON Patch](https://jsonpatch.com/)**: * Is a standard format for describing changes to apply to a JSON document. @@ -46,7 +48,7 @@ These benchmarks highlight significant performance gains and reduced memory usag ## Enable JSON Patch support with `System.Text.Json` -To enable JSON Patch support with `System.Text.Json`, install the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch/) NuGet package. +To enable JSON Patch support with `System.Text.Json`, install the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson) NuGet package. ```dotnetcli dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease From c0ea3dd9e413439b7498df42f8137f448b26a471 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Wed, 11 Jun 2025 13:45:47 -0700 Subject: [PATCH 09/20] Small edits --- aspnetcore/web-api/jsonpatch.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 20a3f47b6f9c..2b9e1dfef900 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -41,7 +41,7 @@ The following benchmarks compare the performance of the new `System.Text.Json` i These benchmarks highlight significant performance gains and reduced memory usage with the new implementation. > [!NOTE] -> The new implementation of `JsonPatch` based on `System.Text.Json` serialization isn't a drop-in replacement for the legacy `Newtonsoft.Json`-based implementation. It doesn't support dynamic types, for example [`ExpandoObject`](/dotnet/api/system.dynamic.expandoobject). +> The new implementation of based on serialization isn't a drop-in replacement for the legacy `Newtonsoft.Json`-based implementation. It doesn't support dynamic types, for example . > [!IMPORTANT] > The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, the new implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. @@ -315,7 +315,7 @@ The previous example results in the following output of the updated object: The `ApplyTo` method generally follows the conventions and options of `System.Text.Json` for processing the `JsonPatchDocument`, including the behavior controlled by the following options: -* `NumberHandling`: Whether numeric properties can be read from strings. +* `NumberHandling`: Whether numeric properties are read from strings. * `PropertyNameCaseInsensitive`: Whether property names are case-sensitive. Key differences between `System.Text.Json` and the new `JsonPatchDocument` implementation: @@ -397,7 +397,7 @@ to the test value 'Jane'. When using the `Microsoft.AspNetCore.JsonPatch.SystemTextJson` package, it's critical to understand and mitigate potential security risks. The following sections outline the identified security risks associated with JSON Patch and provide recommended mitigations to ensure secure usage of the package. > [!IMPORTANT] -> ***This is not an exhaustive list of threats.*** app developers must conduct their own threat model reviews to determine an app-specific comprehensive list and come up with appropriate mitigations as needed. For example, apps which expose collections to patch operations should consider the potential for algorithmic complexity attacks if those operations insert or remove elements at the beginning of the collection. +> ***This is not an exhaustive list of threats.*** App developers must conduct their own threat model reviews to determine an app-specific comprehensive list and come up with appropriate mitigations as needed. For example, apps which expose collections to patch operations should consider the potential for algorithmic complexity attacks if those operations insert or remove elements at the beginning of the collection. By running comprehensive threat models for their own apps and addressing identified threats while following the recommended mitigations below, consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks. @@ -413,7 +413,7 @@ Consumers of these packages can integrate JSON Patch functionality into their ap * **Impact**: Potential Out-Of-Memory (OOM) conditions, causing service disruptions. * **Mitigation**: * Validate incoming JSON Patch documents for size and structure before calling `ApplyTo`. - * The validation needs to be app specific, but an example validation can look similar to the following: + * The validation must be app specific, but an example validation can look similar to the following: ```csharp public void Validate(JsonPatchDocument patch) From 47eab6ae0f5ebfbcf80f7cb462768fb4e1921d75 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Thu, 12 Jun 2025 14:36:50 -0700 Subject: [PATCH 10/20] Added xref links for all API mention. --- aspnetcore/web-api/jsonpatch.md | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 2b9e1dfef900..a2ec543b6294 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -14,7 +14,7 @@ uid: web-api/jsonpatch This article explains how to handle JSON Patch requests in an ASP.NET Core web API. -JSON Patch support in ASP.NET Core web API is based on `System.Text.Json` serialization, and requires the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson) NuGet package. +JSON Patch support in ASP.NET Core web API is based on serialization, and requires the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson) NuGet package. **[JSON Patch](https://jsonpatch.com/)**: @@ -24,12 +24,12 @@ JSON Patch support in ASP.NET Core web API is based on `System.Text.Json` serial In web apps, JSON Patch is commonly used in a PATCH operation to perform partial updates of a resource. Rather than sending the entire resource for an update, clients can send a JSON Patch document containing only the changes. Patching reduces payload size and improves efficiency. -JSON Patch support in ASP.NET Core web API is based on `System.Text.Json` serialization, starting with .NET 10. This release introduces a new implementation of `JsonPatch` based on `System.Text.Json` serialization. This feature: +JSON Patch support in ASP.NET Core web API is based on serialization, starting with .NET 10. This release introduces a new implementation of based on serialization. This feature: -* Aligns with modern .NET practices by leveraging the `System.Text.Json` library, which is optimized for .NET. +* Aligns with modern .NET practices by leveraging the library, which is optimized for .NET. * Provides improved performance and reduced memory usage compared to the legacy `Newtonsoft.Json`-based implementation. For more information on the legacy `Newtonsoft.Json`-based implementation, see the [.NET 9 version of this article](xref:web-api/jsonpatch?view=aspnetcore-9.0&preserve-view=true). -The following benchmarks compare the performance of the new `System.Text.Json` implementation with the legacy `Newtonsoft.Json` implementation: +The following benchmarks compare the performance of the new implementation with the legacy `Newtonsoft.Json` implementation: | Scenario | Implementation | Mean | Allocated Memory | |----------------------------|------------------------|------------|------------------| @@ -46,22 +46,22 @@ These benchmarks highlight significant performance gains and reduced memory usag > [!IMPORTANT] > The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, the new implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. -## Enable JSON Patch support with `System.Text.Json` +## Enable JSON Patch support with -To enable JSON Patch support with `System.Text.Json`, install the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson) NuGet package. +To enable JSON Patch support with , install the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson) NuGet package. ```dotnetcli dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease ``` -This package provides a `JsonPatchDocument` class to represent a JSON Patch document for objects of type `T` and custom logic for serializing and deserializing JSON Patch documents using `System.Text.Json`. The key method of the `JsonPatchDocument` class is `ApplyTo`, which applies the patch operations to a target object of type `T`. +This package provides a class to represent a JSON Patch document for objects of type `T` and custom logic for serializing and deserializing JSON Patch documents using . The key method of the class is , which applies the patch operations to a target object of type `T`. ## Action method code In an API controller, an action method for JSON Patch: -* Is annotated with the `HttpPatch` attribute. -* Accepts a , typically with [`[FromBody]`](xref:Microsoft.AspNetCore.Mvc.FromBodyAttribute). +* Is annotated with the attribute. +* Accepts a , typically with [](xref:Microsoft.AspNetCore.Mvc.FromBodyAttribute). * Calls on the patch document to apply the changes. Here's an example: @@ -85,7 +85,7 @@ The sample action method: ### Model state -The preceding action method example calls an overload of `ApplyTo` that takes model state as one of its parameters. With this option, you can get error messages in responses. The following example shows the body of a 400 Bad Request response for a `test` operation: +The preceding action method example calls an overload of that takes model state as one of its parameters. With this option, you can get error messages in responses. The following example shows the body of a 400 Bad Request response for a `test` operation: ```json { @@ -241,9 +241,9 @@ Example: ## Apply a JSON Patch document to an object -The following examples demonstrate how to use the `ApplyTo` method to apply a JSON Patch document to an object. +The following examples demonstrate how to use the method to apply a JSON Patch document to an object. -### Example: Apply a `JsonPatchDocument` to an object +### Example: Apply a to an object The following example demonstrates: @@ -313,15 +313,15 @@ The previous example results in the following output of the updated object: } ``` -The `ApplyTo` method generally follows the conventions and options of `System.Text.Json` for processing the `JsonPatchDocument`, including the behavior controlled by the following options: +The method generally follows the conventions and options of for processing the , including the behavior controlled by the following options: -* `NumberHandling`: Whether numeric properties are read from strings. -* `PropertyNameCaseInsensitive`: Whether property names are case-sensitive. +* : Whether numeric properties are read from strings. +* : Whether property names are case-sensitive. -Key differences between `System.Text.Json` and the new `JsonPatchDocument` implementation: +Key differences between and the new implementation: -* The runtime type of the target object, not the declared type, determines which properties `ApplyTo` patches. -* `System.Text.Json` deserialization relies on the declared type to identify eligible properties. +* The runtime type of the target object, not the declared type, determines which properties patches. +* deserialization relies on the declared type to identify eligible properties. ### Example: Apply a JsonPatchDocument with error handling @@ -332,7 +332,7 @@ JSON `Patch` supports the `test` operation, which checks if a specified value eq The following example demonstrates how to handle these errors gracefully. > [!Important] -> The object passed to the `ApplyTo` method is modified in place. The caller is responsible for discarding changes if any operation fails. +> The object passed to the method is modified in place. The caller is responsible for discarding changes if any operation fails. ```csharp // Original object @@ -412,7 +412,7 @@ Consumers of these packages can integrate JSON Patch functionality into their ap * **Scenario**: A malicious client submits a `copy` operation that duplicates large object graphs multiple times, leading to excessive memory consumption. * **Impact**: Potential Out-Of-Memory (OOM) conditions, causing service disruptions. * **Mitigation**: - * Validate incoming JSON Patch documents for size and structure before calling `ApplyTo`. + * Validate incoming JSON Patch documents for size and structure before calling . * The validation must be app specific, but an example validation can look similar to the following: ```csharp @@ -433,9 +433,9 @@ public void Validate(JsonPatchDocument patch) * **Scenario**: Patch operations can manipulate fields with implicit invariants, (for example, internal flags, IDs, or computed fields), violating business constraints. * **Impact**: Data integrity issues and unintended app behavior. * **Mitigation**: - * Use POCO objects with explicitly defined properties that are safe to modify. - * Avoid exposing sensitive or security-critical properties in the target object. - * If no POCO object is used, validate the patched object after applying operations to ensure business rules and invariants aren't violated. + * Use POCOs (Plain Old CLR Objects) with explicitly defined properties that are safe to modify. + * Avoid exposing sensitive or security-critical properties in the target object. + * If a POCO object isn't used, validate the patched object after applying operations to ensure business rules and invariants aren't violated. ### Authentication and authorization From f331b6601168e099c6f90b76bf256bac2569fc3e Mon Sep 17 00:00:00 2001 From: wadepickett Date: Thu, 12 Jun 2025 15:12:15 -0700 Subject: [PATCH 11/20] Per review: For v9 moved up moniker to avoid repeate of security section --- aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md b/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md index 5876658fac53..d157ad92a64b 100644 --- a/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md +++ b/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md @@ -531,6 +531,7 @@ To test the sample, run the app and send HTTP requests with the following settin * Header: `Content-Type: application/json-patch+json` * Body: Copy and paste one of the JSON patch document samples from the *JSON* project folder. +:::moniker-end ## Mitigating security risks When using the `Microsoft.AspNetCore.JsonPatch` package with the `Newtonsoft.Json`-based implementation, it's critical to understand and mitigate potential security risks. The following sections outline the identified security risks associated with JSON Patch and provide recommended mitigations to ensure secure usage of the package. @@ -585,4 +586,3 @@ public void Validate(JsonPatchDocument patch) * [IETF RFC 6901 JSON Patch path format spec](https://tools.ietf.org/html/rfc6901) * [ASP.NET Core JSON Patch source code](https://github.com/dotnet/AspNetCore/tree/main/src/Features/JsonPatch/src) -:::moniker-end From 7fae6258a9a04297e743e5cedcf3b4dc5f2a71e4 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Thu, 12 Jun 2025 15:15:45 -0700 Subject: [PATCH 12/20] Per Review: Removed JsonPatch Operations section --- aspnetcore/web-api/jsonpatch.md | 144 -------------------------------- 1 file changed, 144 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index a2ec543b6294..62f5d890f75c 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -95,150 +95,6 @@ The preceding action method example calls an overload of `. - -Example: - -```json -[ - { - "op": "remove", - "path": "/customerName" - }, - { - "op": "remove", - "path": "/orders/0" - } -] -``` - -### The `replace` Operation - -This operation is functionally the same as a `remove` followed by an `add`. - -Example: - -```json -[ - { - "op": "replace", - "path": "/customerName", - "value": "Barry" - }, - { - "op": "replace", - "path": "/orders/0", - "value": { - "orderName": "Order2", - "orderType": null - } - } -] -``` - -### The `move` Operation - -* If `path` points to an array element**: Copies from the element at `from` to the location specified by `path`, then removes the element at `from`. -* If `path` points to a property**: Copies the value of the `from` property to the `path` property, then removes the `from` property. -* If `path` points to a nonexistent property**: - * If the resource to patch is a static object: The request fails. - -Example: - -```json -[ - { - "op": "move", - "from": "/orders/0/orderName", - "path": "/customerName" - }, - { - "op": "move", - "from": "/orders/1", - "path": "/orders/0" - } -] -``` - -### The `copy` Operation - -This operation is functionally the same as a `move` operation without the final `remove` step. - -Example: - -```json -[ - { - "op": "copy", - "from": "/orders/0/orderName", - "path": "/customerName" - }, - { - "op": "copy", - "from": "/orders/1", - "path": "/orders/0" - } -] -``` - -### The `test` Operation - -* If the value at the location indicated by `path` is different from the value provided in `value`, the request fails. In that case, the entire PATCH request fails, even if all other operations in the patch document would otherwise succeed. - -Example: - -```json -[ - { - "op": "test", - "path": "/customerName", - "value": "Nancy" - }, - { - "op": "add", - "path": "/customerName", - "value": "Barry" - } -] -``` - ## Apply a JSON Patch document to an object The following examples demonstrate how to use the method to apply a JSON Patch document to an object. From 6f9b1faef18ba4f83b0dab4906e14e31ecfdda46 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Thu, 12 Jun 2025 15:20:04 -0700 Subject: [PATCH 13/20] Fixed dupe mitigate security section --- .../web-api/jsonpatch/includes/jsonpatch9.md | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md b/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md index d157ad92a64b..73f06f6dfbbf 100644 --- a/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md +++ b/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md @@ -532,57 +532,4 @@ To test the sample, run the app and send HTTP requests with the following settin * Body: Copy and paste one of the JSON patch document samples from the *JSON* project folder. :::moniker-end -## Mitigating security risks - -When using the `Microsoft.AspNetCore.JsonPatch` package with the `Newtonsoft.Json`-based implementation, it's critical to understand and mitigate potential security risks. The following sections outline the identified security risks associated with JSON Patch and provide recommended mitigations to ensure secure usage of the package. - -> [!IMPORTANT] -> ***This is not an exhaustive list of threats.*** App developers must conduct their own threat model reviews to determine an app-specific comprehensive list and come up with appropriate mitigations as needed. For example, apps which expose collections to patch operations should consider the potential for algorithmic complexity attacks if those operations insert or remove elements at the beginning of the collection. - -By running comprehensive threat models for their own apps and addressing identified threats while following the recommended mitigations below, consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks. - -### Denial of Service (DoS) via memory amplification - -* **Scenario**: A malicious client submits a `copy` operation that duplicates large object graphs multiple times, leading to excessive memory consumption. -* **Impact**: Potential Out-Of-Memory (OOM) conditions, causing service disruptions. -* **Mitigation**: - * Validate incoming JSON Patch documents for size and structure before calling `ApplyTo`. - * The validation needs to be app specific, but an example validation can look similar to the following: - -```csharp -public void Validate(JsonPatchDocument patch) -{ - // This is just an example. It's up to the developer to make sure that - // this case is handled properly, based on the app needs. - if (patch.Operations.Where(op => op.OperationType == OperationType.Copy).Count() - > MaxCopyOperationsCount) - { - throw new InvalidOperationException(); - } -} -``` - -### Business Logic Subversion - -* **Scenario**: Patch operations can manipulate fields with implicit invariants (for example, internal flags, IDs, or computed fields), violating business constraints. -* **Impact**: Data integrity issues and unintended app behavior. -* **Mitigation**: - * Use POCO objects with explicitly defined properties that are safe to modify. - * Avoid exposing sensitive or security-critical properties in the target object. - * If no POCO object is used, validate the patched object after applying operations to ensure business rules and invariants aren't violated. - -### Authentication and authorization - -* **Scenario**: Unauthenticated or unauthorized clients send malicious JSON Patch requests. -* **Impact**: Unauthorized access to modify sensitive data or disrupt app behavior. -* **Mitigation**: - * Protect endpoints accepting JSON Patch requests with proper authentication and authorization mechanisms. - * Restrict access to trusted clients or users with appropriate permissions. - -## Additional resources - -* [IETF RFC 5789 PATCH method specification](https://tools.ietf.org/html/rfc5789) -* [IETF RFC 6902 JSON Patch specification](https://tools.ietf.org/html/rfc6902) -* [IETF RFC 6901 JSON Patch path format spec](https://tools.ietf.org/html/rfc6901) -* [ASP.NET Core JSON Patch source code](https://github.com/dotnet/AspNetCore/tree/main/src/Features/JsonPatch/src) From 766520fe20b29b512c73f5bb351ce0daa260aad1 Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Fri, 13 Jun 2025 16:38:13 -0700 Subject: [PATCH 14/20] Apply suggestions from code review Co-authored-by: Tom Dykstra --- aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md b/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md index 73f06f6dfbbf..fc6b9ba8cd95 100644 --- a/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md +++ b/aspnetcore/web-api/jsonpatch/includes/jsonpatch9.md @@ -3,7 +3,7 @@ This article explains how to handle JSON Patch requests in an ASP.NET Core web API. > [!IMPORTANT] -> The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, this implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. +> The JSON Patch standard has ***inherent security risks***. This implementation ***doesn't attempt to mitigate these inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. ## Package installation From 57d4db4934b9deafc5437e95dbdb4c69e92b3cff Mon Sep 17 00:00:00 2001 From: wadepickett Date: Fri, 13 Jun 2025 17:23:07 -0700 Subject: [PATCH 15/20] Applied changes per tdystra recommendations --- aspnetcore/web-api/jsonpatch.md | 38 ++++++++++++++------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 62f5d890f75c..2bb3de56e3b5 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -8,43 +8,39 @@ ms.custom: mvc ms.date: 06/03/2025 uid: web-api/jsonpatch --- -# JsonPatch in ASP.NET Core web API +# JSON Patch support in ASP.NET Core web API :::moniker range=">= aspnetcore-10.0" This article explains how to handle JSON Patch requests in an ASP.NET Core web API. -JSON Patch support in ASP.NET Core web API is based on serialization, and requires the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson) NuGet package. +JSON Patch support in ASP.NET Core web API is based on serialization, and requires the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson) NuGet package. -**[JSON Patch](https://jsonpatch.com/)**: +## What is the JSON Patch standard? + +The JSON Patch standard: * Is a standard format for describing changes to apply to a JSON document. -* Is defined in [RFC 6902] and is widely used in RESTful APIs to perform partial updates to JSON resources. +* Is defined in [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) and is widely used in RESTful APIs to perform partial updates to JSON resources. * Describes a sequence of operations such as `add`, `remove`, `replace`, `move`, `copy`, and `test` that modify a JSON document. In web apps, JSON Patch is commonly used in a PATCH operation to perform partial updates of a resource. Rather than sending the entire resource for an update, clients can send a JSON Patch document containing only the changes. Patching reduces payload size and improves efficiency. -JSON Patch support in ASP.NET Core web API is based on serialization, starting with .NET 10. This release introduces a new implementation of based on serialization. This feature: - -* Aligns with modern .NET practices by leveraging the library, which is optimized for .NET. -* Provides improved performance and reduced memory usage compared to the legacy `Newtonsoft.Json`-based implementation. For more information on the legacy `Newtonsoft.Json`-based implementation, see the [.NET 9 version of this article](xref:web-api/jsonpatch?view=aspnetcore-9.0&preserve-view=true). +For an overview of the JSON Patch standard, see [jsonpatch.com](https://jsonpatch.com/). -The following benchmarks compare the performance of the new implementation with the legacy `Newtonsoft.Json` implementation: +## JSON Patch support in ASP.NET Core web API -| Scenario | Implementation | Mean | Allocated Memory | -|----------------------------|------------------------|------------|------------------| -| **Application Benchmarks** | Newtonsoft.JsonPatch | 271.924 µs | 25 KB | -| | System.Text.JsonPatch | 1.584 µs | 3 KB | -| **Deserialization Benchmarks** | Newtonsoft.JsonPatch | 19.261 µs | 43 KB | -| | System.Text.JsonPatch | 7.917 µs | 7 KB | +JSON Patch support in ASP.NET Core web API is based on serialization, starting with .NET 10, implementing based on serialization. This feature: -These benchmarks highlight significant performance gains and reduced memory usage with the new implementation. +* Requires the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch.SystemTextJson) NuGet package. +* Aligns with modern .NET practices by leveraging the library, which is optimized for .NET. +* Provides improved performance and reduced memory usage compared to the legacy `Newtonsoft.Json`-based implementation. For more information on the legacy `Newtonsoft.Json`-based implementation, see the [.NET 9 version of this article](xref:web-api/jsonpatch?view=aspnetcore-9.0&preserve-view=true). > [!NOTE] -> The new implementation of based on serialization isn't a drop-in replacement for the legacy `Newtonsoft.Json`-based implementation. It doesn't support dynamic types, for example . +> The implementation of based on serialization isn't a drop-in replacement for the legacy `Newtonsoft.Json`-based implementation. It doesn't support dynamic types, for example . > [!IMPORTANT] -> The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, the new implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. +> The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, the ASP.NET Core implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section. ## Enable JSON Patch support with @@ -255,9 +251,7 @@ When using the `Microsoft.AspNetCore.JsonPatch.SystemTextJson` package, it's cri > [!IMPORTANT] > ***This is not an exhaustive list of threats.*** App developers must conduct their own threat model reviews to determine an app-specific comprehensive list and come up with appropriate mitigations as needed. For example, apps which expose collections to patch operations should consider the potential for algorithmic complexity attacks if those operations insert or remove elements at the beginning of the collection. -By running comprehensive threat models for their own apps and addressing identified threats while following the recommended mitigations below, consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks. - -Consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks, including: +To minimize security risks when integrating JSON Patch functionality into their apps, developers should: * Run comprehensive threat models for their own apps. * Address identified threats. @@ -286,7 +280,7 @@ public void Validate(JsonPatchDocument patch) ### Business Logic Subversion -* **Scenario**: Patch operations can manipulate fields with implicit invariants, (for example, internal flags, IDs, or computed fields), violating business constraints. +* **Scenario**: Patch operations can manipulate fields with implicit invariants (for example, internal flags, IDs, or computed fields), violating business constraints. * **Impact**: Data integrity issues and unintended app behavior. * **Mitigation**: * Use POCOs (Plain Old CLR Objects) with explicitly defined properties that are safe to modify. From edd8b747d1e3dfd75ac77b35debc8004dee5eb04 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Mon, 16 Jun 2025 09:54:16 -0700 Subject: [PATCH 16/20] Removed real app warning --- aspnetcore/web-api/jsonpatch.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 2bb3de56e3b5..03f4e191d9c6 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -22,7 +22,14 @@ The JSON Patch standard: * Is a standard format for describing changes to apply to a JSON document. * Is defined in [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) and is widely used in RESTful APIs to perform partial updates to JSON resources. -* Describes a sequence of operations such as `add`, `remove`, `replace`, `move`, `copy`, and `test` that modify a JSON document. +* Describes a sequence of operations that modify a JSON document such as: + + * `add` + * `remove` + * `replace` + * `move` + * `copy` + * `test` In web apps, JSON Patch is commonly used in a PATCH operation to perform partial updates of a resource. Rather than sending the entire resource for an update, clients can send a JSON Patch document containing only the changes. Patching reduces payload size and improves efficiency. @@ -64,7 +71,7 @@ Here's an example: :::code language="csharp" source="~/web-api/jsonpatch/samples/3.x/api/Controllers/HomeController.cs" id="snippet_PatchAction" highlight="1,3,9"::: -This code from the sample app works with the following `Customer` model: +This code from the sample app works with the following `Customer` and `Order` models: :::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/Models/Customer.cs"::: @@ -76,9 +83,6 @@ The sample action method: * Applies the patch. * Returns the result in the body of the response. -> [!NOTE] -> In a real app, the data would typically be retrieved from a store such as a database. After applying the patch, the updated data would be saved back to the database. - ### Model state The preceding action method example calls an overload of that takes model state as one of its parameters. With this option, you can get error messages in responses. The following example shows the body of a 400 Bad Request response for a `test` operation: From 5a9b06eedef2ff176581ab1654bf2fa99631c2a3 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Tue, 17 Jun 2025 19:47:47 -0700 Subject: [PATCH 17/20] Added Mike's sample and updated action method section --- aspnetcore/web-api/jsonpatch.md | 32 ++++-- .../10.x/Controllers/CustomerController.cs | 57 ++++++++++ .../jsonpatch/samples/10.x/CustomerApi.cs | 52 +++++++++ .../jsonpatch/samples/10.x/Data/AppDb.cs | 22 ++++ .../samples/10.x/Data/AppDbSeeder.cs | 53 +++++++++ .../samples/10.x/JsonPatchSample.csproj | 16 +++ .../samples/10.x/JsonPatchSample.http | 76 +++++++++++++ .../jsonpatch/samples/10.x/Models/Customer.cs | 16 +++ .../jsonpatch/samples/10.x/Models/Order.cs | 14 +++ .../web-api/jsonpatch/samples/10.x/Program.cs | 35 ++++++ .../10.x/api/Controllers/HomeController.cs | 104 ------------------ .../samples/10.x/api/JsonPatchSample.csproj | 17 --- .../samples/10.x/api/Models/Category.cs | 9 -- .../samples/10.x/api/Models/Customer.cs | 7 -- .../samples/10.x/api/Models/Order.cs | 7 -- .../samples/10.x/api/Models/Product.cs | 8 -- .../jsonpatch/samples/10.x/api/MyJPIF.cs | 24 ---- .../jsonpatch/samples/10.x/api/Program.cs | 42 ------- ...ings.json => appsettings.Development.json} | 3 +- .../jsonpatch/samples/10.x/appsettings.json | 12 ++ 20 files changed, 374 insertions(+), 232 deletions(-) create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/Controllers/CustomerController.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/CustomerApi.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDb.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDbSeeder.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.csproj create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.http create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/Models/Customer.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/Models/Order.cs create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/Program.cs delete mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Controllers/HomeController.cs delete mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/JsonPatchSample.csproj delete mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Category.cs delete mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Customer.cs delete mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Order.cs delete mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Product.cs delete mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/MyJPIF.cs delete mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/api/Program.cs rename aspnetcore/web-api/jsonpatch/samples/10.x/{api/appsettings.json => appsettings.Development.json} (80%) create mode 100644 aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.json diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 03f4e191d9c6..8ea47ed2cb16 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -59,7 +59,7 @@ dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease This package provides a class to represent a JSON Patch document for objects of type `T` and custom logic for serializing and deserializing JSON Patch documents using . The key method of the class is , which applies the patch operations to a target object of type `T`. -## Action method code +## Controller Action method code applying JSON Patch In an API controller, an action method for JSON Patch: @@ -67,30 +67,38 @@ In an API controller, an action method for JSON Patch: * Accepts a , typically with [](xref:Microsoft.AspNetCore.Mvc.FromBodyAttribute). * Calls on the patch document to apply the changes. -Here's an example: +### Example Action method: -:::code language="csharp" source="~/web-api/jsonpatch/samples/3.x/api/Controllers/HomeController.cs" id="snippet_PatchAction" highlight="1,3,9"::: +:::code language="csharp" source="~/web-api/jsonpatch/samples/10.x/JsonPatchSample/Controllers/CustomerController.cs" id="snippet_PatchAction" highlight="1,2,14-19"::: This code from the sample app works with the following `Customer` and `Order` models: -:::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/Models/Customer.cs"::: +:::code language="csharp" source="~/web-api/jsonpatch/samples/10.x/api/Models/Customer.cs"::: -:::code language="csharp" source="~/web-api/jsonpatch/samples/6.x/api/Models/Order.cs"::: +:::code language="csharp" source="~/web-api/jsonpatch/samples/10.x/api/Models/Order.cs"::: -The sample action method: +The sample action method's key steps: -* Constructs a `Customer`. -* Applies the patch. -* Returns the result in the body of the response. +* **Retrieve the Customer**: + * The method retrieves a `Customer` object from the database `AppDb` using the provided id. + * If no `Customer` object is found, it returns a `404 Not Found` response. +* **Apply JSON Patch**: + * The method applies the JSON Patch operations from the patchDoc to the retrieved `Customer` object. + * If errors occur during the patch application, such as invalid operations or conflicts, they are captured by an error handling delegate. This delegate adds error messages to the `ModelState` using the type name of the affected object and the error message. +* **Validate ModelState**: + * After applying the patch, the method checks the `ModelState` for errors. + * If the `ModelState` is invalid, such as due to patch errors, it returns a `400 Bad Request` response with the validation errors. +* **Return the Updated Customer**: + * If the patch is successfully applied and the `ModelState` is valid, the method returns the updated `Customer` object in the response. -### Model state +### Example error response: -The preceding action method example calls an overload of that takes model state as one of its parameters. With this option, you can get error messages in responses. The following example shows the body of a 400 Bad Request response for a `test` operation: +The following example shows the body of a `400 Bad Request` response for a JSON Patch operation when the specified path is invalid: ```json { "Customer": [ - "The current value 'John' at path 'customerName' != test value 'Nancy'." + "The target location specified by path segment 'foobar' was not found." ] } ``` diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Controllers/CustomerController.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/Controllers/CustomerController.cs new file mode 100644 index 000000000000..f1b1255f9182 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/Controllers/CustomerController.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.JsonPatch.SystemTextJson; + +using App.Data; +using App.Models; + +namespace App.Controllers; + +[ApiController] +[Route("/api/customers")] +public class CustomerController : ControllerBase +{ + [HttpGet("{id}", Name = "GetCustomer")] + public Customer Get(AppDb db, string id) + { + // Retrieve the customer by ID + var customer = db.Customers.FirstOrDefault(c => c.Id == id); + + // Return 404 Not Found if customer doesn't exist + if (customer == null) + { + Response.StatusCode = 404; + return null; + } + + return customer; + } + + // + [HttpPatch("{id}", Name = "UpdateCustomer")] + public IActionResult Update(AppDb db, string id, [FromBody] JsonPatchDocument patchDoc) + { + // Retrieve the customer by ID + var customer = db.Customers.FirstOrDefault(c => c.Id == id); + + // Return 404 Not Found if customer doesn't exist + if (customer == null) + { + return NotFound(); + } + + patchDoc.ApplyTo(customer, jsonPatchError => + { + var key = jsonPatchError.AffectedObject.GetType().Name; + ModelState.AddModelError(key, jsonPatchError.ErrorMessage); + } + ); + + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return new ObjectResult(customer); + } + // +} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/CustomerApi.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/CustomerApi.cs new file mode 100644 index 000000000000..693d29955f9c --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/CustomerApi.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.JsonPatch.SystemTextJson; +using Microsoft.EntityFrameworkCore; + +using App.Data; +using App.Models; + +internal static class CustomerApi { + public static void MapCustomerApi(this IEndpointRouteBuilder routes) + { + var group = routes.MapGroup("/customers").WithTags("Customers"); + + group.MapGet("/{id}", async Task, NotFound>> (AppDb db, string id) => + { + return await db.Customers.Include(c => c.Orders).FirstOrDefaultAsync(c => c.Id == id) is Customer customer + ? TypedResults.Ok(customer) + : TypedResults.NotFound(); + }); + + group.MapPatch("/{id}", async Task,NotFound,BadRequest, ValidationProblem>> (AppDb db, string id, + JsonPatchDocument patchDoc) => + { + var customer = await db.Customers.Include(c => c.Orders).FirstOrDefaultAsync(c => c.Id == id); + if (customer is null) + { + return TypedResults.NotFound(); + } + if (patchDoc != null) + { + Dictionary? errors = null; + patchDoc.ApplyTo(customer, jsonPatchError => + { + errors ??= new (); + var key = jsonPatchError.AffectedObject.GetType().Name; + if (!errors.ContainsKey(key)) + { + errors.Add(key, new string[] { }); + } + errors[key] = errors[key].Append(jsonPatchError.ErrorMessage).ToArray(); + }); + if (errors != null) + { + return TypedResults.ValidationProblem(errors); + } + await db.SaveChangesAsync(); + } + + return TypedResults.Ok(customer); + }) + .Accepts>("application/json-patch+json"); + } +} \ No newline at end of file diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDb.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDb.cs new file mode 100644 index 000000000000..45f39d082413 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDb.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using App.Models; + +namespace App.Data; + +public class AppDb : DbContext +{ + public required DbSet Customers { get; set; } + + public AppDb(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Configure entity relationships here if needed + modelBuilder.Entity() + .HasKey(c => c.Id); + } +} \ No newline at end of file diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDbSeeder.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDbSeeder.cs new file mode 100644 index 000000000000..d68222a464a7 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDbSeeder.cs @@ -0,0 +1,53 @@ +using App.Models; +using Microsoft.EntityFrameworkCore; + +namespace App.Data; + +public static class AppDbSeeder +{ + public static async Task Seed(WebApplication app) + { + // Create and seed the database + using (var scope = app.Services.CreateScope()) + { + var services = scope.ServiceProvider; + var context = services.GetRequiredService(); + context.Database.EnsureCreated(); + + if (context.Customers.Any()) + { + return; + } + + Customer[] customers = { + new Customer + { + Id = "1", + Name = "John Doe", + Email = "john.doe@example.com", + PhoneNumber = "555-123-4567", + Address = "123 Main St, Anytown, USA" + }, + new Customer + { + Id = "2", + Name = "Jane Smith", + Email = "jane.smith@example.com", + PhoneNumber = "555-987-6543", + Address = "456 Oak Ave, Somewhere, USA" + }, + new Customer + { + Id = "3", + Name = "Bob Johnson", + Email = "bob.johnson@example.com", + PhoneNumber = "555-555-5555", + Address = "789 Pine Rd, Elsewhere, USA" + } + }; + + await context.Customers.AddRangeAsync(customers); + await context.SaveChangesAsync(); + } + } +} \ No newline at end of file diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.csproj b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.csproj new file mode 100644 index 000000000000..ae1404b68190 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.csproj @@ -0,0 +1,16 @@ + + + + net10.0 + enable + enable + + + + + + + + + + diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.http b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.http new file mode 100644 index 000000000000..eb981fd996be --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.http @@ -0,0 +1,76 @@ +@HostAddress = http://localhost:5221 + +GET {{HostAddress}}/openapi/v1.json +Accept: application/json + +### + +GET {{HostAddress}}/api/customers/1 +Accept: application/json + +### + +PATCH {{HostAddress}}/api/customers/1 +Content-Type: application/json-patch+json +Accept: application/json + +[ + { + "op": "replace", + "path": "/email", + "value": "foo@bar.baz" + } +] + +### + +# Error response + +PATCH {{HostAddress}}/api/customers/1 +Content-Type: application/json-patch+json +Accept: application/json + +[ + { + "op": "add", + "path": "/foobar", + "value": 42 + } +] + +### +### Minimal API requests +### + +GET {{HostAddress}}/customers/1 +Accept: application/json + +### + +PATCH {{HostAddress}}/customers/1 +Content-Type: application/json-patch+json +Accept: application/json + +[ + { + "op": "replace", + "path": "/email", + "value": "foo@bar.baz" + } +] + +### + +# Error response + +PATCH {{HostAddress}}/customers/1 +Content-Type: application/json-patch+json +Accept: application/json + +[ + { + "op": "add", + "path": "/foobar", + "value": 42 + } +] diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Models/Customer.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/Models/Customer.cs new file mode 100644 index 000000000000..1af9e4fda0d3 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/Models/Customer.cs @@ -0,0 +1,16 @@ +namespace App.Models; + +public class Customer +{ + public string Id { get; set; } + public string? Name { get; set; } + public string? Email { get; set; } + public string? PhoneNumber { get; set; } + public string? Address { get; set; } + public List? Orders { get; set; } + + public Customer() + { + Id = Guid.NewGuid().ToString(); + } +} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Models/Order.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/Models/Order.cs new file mode 100644 index 000000000000..876f7b7c24d3 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/Models/Order.cs @@ -0,0 +1,14 @@ +namespace App.Models; + +public class Order +{ + public string Id { get; set; } + public DateTime? OrderDate { get; set; } + public DateTime? ShipDate { get; set; } + public decimal TotalAmount { get; set; } + + public Order() + { + Id = Guid.NewGuid().ToString(); + } +} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Program.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/Program.cs new file mode 100644 index 000000000000..1abd02b425a7 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/Program.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore; + +using App.Data; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Add the CatalogContext to the DI container +builder.Services.AddDbContext(options => + options.UseSqlite(builder.Configuration.GetConnectionString("AppDb"))); + +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +await AppDbSeeder.Seed(app); + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.MapCustomerApi(); + +app.Run(); diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Controllers/HomeController.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Controllers/HomeController.cs deleted file mode 100644 index cf7eb4badad3..000000000000 --- a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Controllers/HomeController.cs +++ /dev/null @@ -1,104 +0,0 @@ -using JsonPatchSample.Models; -using Microsoft.AspNetCore.JsonPatch; -using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; -using System.Dynamic; - -namespace JsonPatchSample.Controllers; - -[Route("jsonpatch/[action]")] -[ApiController] -public class HomeController : ControllerBase -{ - // - [HttpPatch] - public IActionResult JsonPatchWithModelState( - [FromBody] JsonPatchDocument patchDoc) - { - if (patchDoc != null) - { - var customer = CreateCustomer(); - - patchDoc.ApplyTo(customer, ModelState); - - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - return new ObjectResult(customer); - } - else - { - return BadRequest(ModelState); - } - } - // - - [HttpPatch] - public IActionResult JsonPatchWithModelStateAndPrefix( - [FromBody] JsonPatchDocument patchDoc, - string prefix) - { - var customer = CreateCustomer(); - - patchDoc.ApplyTo(customer, ModelState, prefix); - - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - return new ObjectResult(customer); - } - - [HttpPatch] - public IActionResult JsonPatchWithoutModelState([FromBody] JsonPatchDocument patchDoc) - { - var customer = CreateCustomer(); - - patchDoc.ApplyTo(customer); - - return new ObjectResult(customer); - } - - [HttpPatch] - public IActionResult JsonPatchForProduct([FromBody] JsonPatchDocument patchDoc) - { - var product = new Product(); - - patchDoc.ApplyTo(product); - - return new ObjectResult(product); - } - - // - [HttpPatch] - public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch) - { - dynamic obj = new ExpandoObject(); - patch.ApplyTo(obj); - - return Ok(obj); - } - // - - private Customer CreateCustomer() - { - return new Customer - { - CustomerName = "John", - Orders = new List() - { - new Order - { - OrderName = "Order0" - }, - new Order - { - OrderName = "Order1" - } - } - }; - } -} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/JsonPatchSample.csproj b/aspnetcore/web-api/jsonpatch/samples/10.x/api/JsonPatchSample.csproj deleted file mode 100644 index 9fdb95013b67..000000000000 --- a/aspnetcore/web-api/jsonpatch/samples/10.x/api/JsonPatchSample.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net6.0 - enable - enable - - - - - - - - - - - diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Category.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Category.cs deleted file mode 100644 index 108f5ae39e1e..000000000000 --- a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Category.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Newtonsoft.Json; - -namespace JsonPatchSample.Models -{ - public class Category - { - public string CategoryName { get; set; } - } -} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Customer.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Customer.cs deleted file mode 100644 index d64fd761dc7c..000000000000 --- a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Customer.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JsonPatchSample.Models; - -public class Customer -{ - public string? CustomerName { get; set; } - public List? Orders { get; set; } -} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Order.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Order.cs deleted file mode 100644 index d3ce1355e151..000000000000 --- a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Order.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JsonPatchSample.Models; - -public class Order -{ - public string OrderName { get; set; } - public string OrderType { get; set; } -} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Product.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Product.cs deleted file mode 100644 index 5ea1f5085e4c..000000000000 --- a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Models/Product.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace JsonPatchSample.Models -{ - public class Product - { - public string ProductName { get; set; } - public Category ProductCategory { get; set; } - } -} diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/MyJPIF.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/MyJPIF.cs deleted file mode 100644 index 6a20608945c0..000000000000 --- a/aspnetcore/web-api/jsonpatch/samples/10.x/api/MyJPIF.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.Extensions.Options; - -namespace JsonPatchSample; - -public static class MyJPIF -{ - public static NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter() - { - var builder = new ServiceCollection() - .AddLogging() - .AddMvc() - .AddNewtonsoftJson() - .Services.BuildServiceProvider(); - - return builder - .GetRequiredService>() - .Value - .InputFormatters - .OfType() - .First(); - } -} \ No newline at end of file diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Program.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/api/Program.cs deleted file mode 100644 index 6b755abe0c02..000000000000 --- a/aspnetcore/web-api/jsonpatch/samples/10.x/api/Program.cs +++ /dev/null @@ -1,42 +0,0 @@ -#define BOTH // FIRST BOTH -#if NEVER -#elif FIRST -// -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddControllers() - .AddNewtonsoftJson(); - -var app = builder.Build(); - -app.UseHttpsRedirection(); - -app.UseAuthorization(); - -app.MapControllers(); - -app.Run(); -// -#elif BOTH -// -using JsonPatchSample; -using Microsoft.AspNetCore.Mvc.Formatters; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddControllers(options => -{ - options.InputFormatters.Insert(0, MyJPIF.GetJsonPatchInputFormatter()); -}); - -var app = builder.Build(); - -app.UseHttpsRedirection(); - -app.UseAuthorization(); - -app.MapControllers(); - -app.Run(); -// -#endif diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/api/appsettings.json b/aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.Development.json similarity index 80% rename from aspnetcore/web-api/jsonpatch/samples/10.x/api/appsettings.json rename to aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.Development.json index 10f68b8c8b4f..0c208ae9181e 100644 --- a/aspnetcore/web-api/jsonpatch/samples/10.x/api/appsettings.json +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.Development.json @@ -4,6 +4,5 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } - }, - "AllowedHosts": "*" + } } diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.json b/aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.json new file mode 100644 index 000000000000..ee5013eca103 --- /dev/null +++ b/aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.json @@ -0,0 +1,12 @@ +{ + "ConnectionStrings": { + "AppDb": "Data Source=app.db" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 4a6deac8eb3f38ab674715b9cd48a0c58d979603 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Tue, 17 Jun 2025 19:59:40 -0700 Subject: [PATCH 18/20] Moved app sample to JsonPatchSample folder --- aspnetcore/web-api/jsonpatch.md | 8 ++++---- .../Controllers/CustomerController.cs | 0 .../samples/10.x/{ => JsonPatchSample}/CustomerApi.cs | 0 .../samples/10.x/{ => JsonPatchSample}/Data/AppDb.cs | 0 .../10.x/{ => JsonPatchSample}/Data/AppDbSeeder.cs | 0 .../10.x/{ => JsonPatchSample}/JsonPatchSample.csproj | 0 .../10.x/{ => JsonPatchSample}/JsonPatchSample.http | 0 .../samples/10.x/{ => JsonPatchSample}/Models/Customer.cs | 0 .../samples/10.x/{ => JsonPatchSample}/Models/Order.cs | 0 .../samples/10.x/{ => JsonPatchSample}/Program.cs | 0 .../{ => JsonPatchSample}/appsettings.Development.json | 0 .../samples/10.x/{ => JsonPatchSample}/appsettings.json | 0 12 files changed, 4 insertions(+), 4 deletions(-) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/Controllers/CustomerController.cs (100%) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/CustomerApi.cs (100%) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/Data/AppDb.cs (100%) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/Data/AppDbSeeder.cs (100%) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/JsonPatchSample.csproj (100%) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/JsonPatchSample.http (100%) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/Models/Customer.cs (100%) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/Models/Order.cs (100%) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/Program.cs (100%) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/appsettings.Development.json (100%) rename aspnetcore/web-api/jsonpatch/samples/10.x/{ => JsonPatchSample}/appsettings.json (100%) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 8ea47ed2cb16..8b51a4bcd576 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -59,7 +59,7 @@ dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease This package provides a class to represent a JSON Patch document for objects of type `T` and custom logic for serializing and deserializing JSON Patch documents using . The key method of the class is , which applies the patch operations to a target object of type `T`. -## Controller Action method code applying JSON Patch +## Action method code applying JSON Patch In an API controller, an action method for JSON Patch: @@ -67,15 +67,15 @@ In an API controller, an action method for JSON Patch: * Accepts a , typically with [](xref:Microsoft.AspNetCore.Mvc.FromBodyAttribute). * Calls on the patch document to apply the changes. -### Example Action method: +### Example Controller Action method: :::code language="csharp" source="~/web-api/jsonpatch/samples/10.x/JsonPatchSample/Controllers/CustomerController.cs" id="snippet_PatchAction" highlight="1,2,14-19"::: This code from the sample app works with the following `Customer` and `Order` models: -:::code language="csharp" source="~/web-api/jsonpatch/samples/10.x/api/Models/Customer.cs"::: +:::code language="csharp" source="~/web-api/jsonpatch/samples/10.x/JsonPatchSample/Models/Customer.cs"::: -:::code language="csharp" source="~/web-api/jsonpatch/samples/10.x/api/Models/Order.cs"::: +:::code language="csharp" source="~/web-api/jsonpatch/samples/10.x/JsonPatchSample/Models/Order.cs"::: The sample action method's key steps: diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Controllers/CustomerController.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Controllers/CustomerController.cs similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/Controllers/CustomerController.cs rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Controllers/CustomerController.cs diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/CustomerApi.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/CustomerApi.cs similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/CustomerApi.cs rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/CustomerApi.cs diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDb.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Data/AppDb.cs similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDb.cs rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Data/AppDb.cs diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDbSeeder.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Data/AppDbSeeder.cs similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/Data/AppDbSeeder.cs rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Data/AppDbSeeder.cs diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.csproj b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/JsonPatchSample.csproj similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.csproj rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/JsonPatchSample.csproj diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.http b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/JsonPatchSample.http similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample.http rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/JsonPatchSample.http diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Models/Customer.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Models/Customer.cs similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/Models/Customer.cs rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Models/Customer.cs diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Models/Order.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Models/Order.cs similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/Models/Order.cs rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Models/Order.cs diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/Program.cs b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Program.cs similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/Program.cs rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/Program.cs diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.Development.json b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/appsettings.Development.json similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.Development.json rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/appsettings.Development.json diff --git a/aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.json b/aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/appsettings.json similarity index 100% rename from aspnetcore/web-api/jsonpatch/samples/10.x/appsettings.json rename to aspnetcore/web-api/jsonpatch/samples/10.x/JsonPatchSample/appsettings.json From 6604d2c960c378422876534a5865640cf9f6995f Mon Sep 17 00:00:00 2001 From: Wade Pickett Date: Tue, 24 Jun 2025 15:33:27 -0700 Subject: [PATCH 19/20] Update aspnetcore/web-api/jsonpatch.md Co-authored-by: Mike Kistler --- aspnetcore/web-api/jsonpatch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 8b51a4bcd576..4228619ad3ca 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -57,7 +57,7 @@ To enable JSON Patch support with , install the [`Microso dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease ``` -This package provides a class to represent a JSON Patch document for objects of type `T` and custom logic for serializing and deserializing JSON Patch documents using . The key method of the class is , which applies the patch operations to a target object of type `T`. +This package provides a class to represent a JSON Patch document for objects of type `T` and custom logic for serializing and deserializing JSON Patch documents using . The key method of the class is , which applies the patch operations to a target object of type `T`. ## Action method code applying JSON Patch From 511a0b6daa6c7cf88036a00d98d58266fc8b1ae3 Mon Sep 17 00:00:00 2001 From: wadepickett Date: Tue, 24 Jun 2025 16:08:54 -0700 Subject: [PATCH 20/20] Updated JsonPatch. to JsonPatch.SystemTextJson. throughout jsonpatch.md --- aspnetcore/web-api/jsonpatch.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/aspnetcore/web-api/jsonpatch.md b/aspnetcore/web-api/jsonpatch.md index 4228619ad3ca..865a6c883e1a 100644 --- a/aspnetcore/web-api/jsonpatch.md +++ b/aspnetcore/web-api/jsonpatch.md @@ -64,8 +64,8 @@ This package provides a attribute. -* Accepts a , typically with [](xref:Microsoft.AspNetCore.Mvc.FromBodyAttribute). -* Calls on the patch document to apply the changes. +* Accepts a , typically with [](xref:Microsoft.AspNetCore.Mvc.FromBodyAttribute). +* Calls on the patch document to apply the changes. ### Example Controller Action method: @@ -83,7 +83,7 @@ The sample action method's key steps: * The method retrieves a `Customer` object from the database `AppDb` using the provided id. * If no `Customer` object is found, it returns a `404 Not Found` response. * **Apply JSON Patch**: - * The method applies the JSON Patch operations from the patchDoc to the retrieved `Customer` object. + * The method applies the JSON Patch operations from the patchDoc to the retrieved `Customer` object. * If errors occur during the patch application, such as invalid operations or conflicts, they are captured by an error handling delegate. This delegate adds error messages to the `ModelState` using the type name of the affected object and the error message. * **Validate ModelState**: * After applying the patch, the method checks the `ModelState` for errors. @@ -105,9 +105,9 @@ The following example shows the body of a `400 Bad Request` response for a JSON ## Apply a JSON Patch document to an object -The following examples demonstrate how to use the method to apply a JSON Patch document to an object. +The following examples demonstrate how to use the method to apply a JSON Patch document to an object. -### Example: Apply a to an object +### Example: Apply a to an object The following example demonstrates: @@ -177,14 +177,14 @@ The previous example results in the following output of the updated object: } ``` -The method generally follows the conventions and options of for processing the , including the behavior controlled by the following options: +The method generally follows the conventions and options of for processing the , including the behavior controlled by the following options: * : Whether numeric properties are read from strings. * : Whether property names are case-sensitive. -Key differences between and the new implementation: +Key differences between and the new implementation: -* The runtime type of the target object, not the declared type, determines which properties patches. +* The runtime type of the target object, not the declared type, determines which properties patches. * deserialization relies on the declared type to identify eligible properties. ### Example: Apply a JsonPatchDocument with error handling @@ -196,7 +196,7 @@ JSON `Patch` supports the `test` operation, which checks if a specified value eq The following example demonstrates how to handle these errors gracefully. > [!Important] -> The object passed to the method is modified in place. The caller is responsible for discarding changes if any operation fails. +> The object passed to the method is modified in place. The caller is responsible for discarding changes if any operation fails. ```csharp // Original object @@ -274,7 +274,7 @@ To minimize security risks when integrating JSON Patch functionality into their * **Scenario**: A malicious client submits a `copy` operation that duplicates large object graphs multiple times, leading to excessive memory consumption. * **Impact**: Potential Out-Of-Memory (OOM) conditions, causing service disruptions. * **Mitigation**: - * Validate incoming JSON Patch documents for size and structure before calling . + * Validate incoming JSON Patch documents for size and structure before calling . * The validation must be app specific, but an example validation can look similar to the following: ```csharp