JsonPatch en la API web de ASP.NET Core
En este artículo se explica cómo administrar solicitudes JSON Patch en una API web ASP.NET Core.
Instalación del paquete
La compatibilidad con JSON Patch en una API web ASP.NET Core se basa en Newtonsoft.Json
y requiere el paquete de NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson
. Para habilitar la compatibilidad con JSON Patch:
Instale el paquete NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson
.Llame a AddNewtonsoftJson. Por ejemplo:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers() .AddNewtonsoftJson(); var app = builder.Build(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
AddNewtonsoftJson
reemplaza los formateadores de entrada y salida basados en System.Text.Json
, que se usan para dar formato a todo el contenido JSON. Este método de extensión es compatible con los siguientes métodos de registro del servicio MVC:
JsonPatch requiere que el encabezado Content-Type
se establezca en application/json-patch+json
.
Agregar compatibilidad con JSON Patch al usar System.Text.Json
El formateador de entrada basado en System.Text.Json
no admite JSON Patch. Para agregar compatibilidad con JSON Patch mediante Newtonsoft.Json
, sin cambiar los demás formateadores de entrada y salida:
Instale el paquete NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson
.Actualice
Program.cs
: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();
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<IOptions<MvcOptions>>() .Value .InputFormatters .OfType<NewtonsoftJsonPatchInputFormatter>() .First(); } }
El código anterior crea una instancia de NewtonsoftJsonPatchInputFormatter y la inserta como la primera entrada de la colección MvcOptions.InputFormatters. Este orden de registro garantiza que:
-
NewtonsoftJsonPatchInputFormatter
procesa solicitudes JSON Patch. - Los formateadores de entradas existentes basados en
System.Text.Json
procesen todas las demás solicitudes y respuestas JSON.
Utilice el método Newtonsoft.Json.JsonConvert.SerializeObject
para serializar un token de seguridad JsonPatchDocument.
Método de solicitud HTTP PATCH
Los métodos PUT y PATCH se usan para actualizar un recurso existente. La diferencia entre ellos es que PUT reemplaza el recurso entero, mientras que PATCH especifica únicamente los cambios.
Revisión JSON
JSON Patch es un formato para especificar las actualizaciones que se aplicarán a un recurso. Un documento JSON Patch tiene una matriz de operaciones. Cada operación identifica un tipo determinado de cambio. Algunos ejemplos de estos cambios incluyen agregar un elemento de matriz o reemplazar un valor de propiedad.
Por ejemplo, los documentos JSON siguientes representan un recurso, un documento JSON Patch para el recurso y el resultado de aplicar las operaciones de actualización.
Ejemplo de recursos
{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}
Ejemplo de revisión de JSON
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
En el código JSON anterior:
- La propiedad
op
indica el tipo de operación. - La propiedad
path
indica el elemento que se va a actualizar. - La propiedad
value
proporciona el nuevo valor.
Recurso después de la revisión
Este es el recurso después de aplicar el documento JSON Patch anterior:
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
Los cambios realizados mediante la aplicación de un documento JSON Patch a un recurso son atómicos. Si cualquier operación en la lista falla, no se aplica ninguna operación de la lista.
Sintaxis de path
La propiedad path de un objeto de operación tiene barras inversas entre niveles. Por ejemplo, "/address/zipCode"
.
Para especificar elementos de matriz se usan índices de base cero. El primer elemento de la matriz addresses
estaría en /addresses/0
. Para usar add
al final de una matriz, use un guion (-
) en lugar de un número de índice: /addresses/-
.
Operations
En la siguiente tabla se muestran las operaciones admitidas, como se ha definido en la especificación de JSON Patch:
Operación | Notas |
---|---|
add |
Agrega un elemento de propiedad o matriz. Para la propiedad existente: establece el valor. |
remove |
Quita un elemento de propiedad o matriz. |
replace |
Lo mismo que remove seguido de add en la misma ubicación. |
move |
Lo mismo que remove desde el origen seguido de add al destino mediante el valor del origen. |
copy |
Lo mismo que add al destino mediante el valor del origen. |
test |
Devuelve el código de estado correcto si el valor en path = al value proporcionado. |
JSON Patch en ASP.NET Core
La implementación de ASP.NET Core de JSON Patch se proporciona en el paquete NuGet Microsoft.AspNetCore.JsonPatch.
Código del método de acción
En un controlador de API, un método de acción para JSON Patch:
- Se anota con el atributo
HttpPatch
. - Acepta JsonPatchDocument<TModel>, normalmente con
[FromBody]
. - Llama a ApplyTo(Object) en el documento de revisión para aplicar los cambios.
Este es un ejemplo:
[HttpPatch]
public IActionResult JsonPatchWithModelState(
[FromBody] JsonPatchDocument<Customer> 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);
}
}
Este código de la aplicación de ejemplo funciona con el siguiente modelo Customer
:
namespace JsonPatchSample.Models;
public class Customer
{
public string? CustomerName { get; set; }
public List<Order>? Orders { get; set; }
}
namespace JsonPatchSample.Models;
public class Order
{
public string OrderName { get; set; }
public string OrderType { get; set; }
}
El método de acción de ejemplo:
- Construye un objeto
Customer
. - Aplica la revisión.
- Devuelve el resultado en el cuerpo de la respuesta.
En una aplicación real, el código recuperaría los datos de un almacén como una base de datos y actualizaría la base de datos después de aplicar la revisión.
Estado del modelo
En el ejemplo anterior del método de acción, se llama a una sobrecarga de ApplyTo
que toma el estado del modelo como uno de sus parámetros. Con esta opción, puede obtener mensajes de error en las respuestas. En el ejemplo siguiente se muestra el cuerpo de una respuesta 400 Solicitud incorrecta de una operación test
:
{
"Customer": [
"The current value 'John' at path 'customerName' != test value 'Nancy'."
]
}
Objetos dinámicos
En el ejemplo siguiente de método de acción, se muestra cómo aplicar una revisión a un objeto dinámico:
[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
dynamic obj = new ExpandoObject();
patch.ApplyTo(obj);
return Ok(obj);
}
La operación add
- Si
path
apunta a un elemento de matriz: inserta un nuevo elemento delante del especificado porpath
. - Si
path
apunta a una propiedad: establece el valor de la propiedad. - Si
path
apunta a una ubicación que no existe:- Si el recurso para revisar es un objeto dinámico: agrega una propiedad.
- Si el recurso para revisar es un objeto estático: la solicitud produce un error.
El siguiente documento de revisión de ejemplo establece el valor de CustomerName
y agrega un objeto Order
al final de la matriz Orders
.
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
La operación remove
- Si
path
apunta a un elemento de matriz: quita el elemento. - Si
path
apunta a una propiedad:- Si el recurso para revisar es un objeto dinámico: quita la propiedad.
- Si el recurso para revisar es un objeto estático:
- Si la propiedad acepta valores NULL: la establece en null.
- Si la propiedad es distinta de null, la establece en
default<T>
.
En el siguiente documento de revisión de ejemplo, se establece CustomerName
en null y se elimina Orders[0]
:
[
{
"op": "remove",
"path": "/customerName"
},
{
"op": "remove",
"path": "/orders/0"
}
]
La operación replace
Esta operación es funcionalmente igual que remove
seguida de add
.
En el siguiente documento de revisión de ejemplo, se establece el valor de CustomerName
y se reemplaza Orders[0]
por un nuevo objeto Order
:
[
{
"op": "replace",
"path": "/customerName",
"value": "Barry"
},
{
"op": "replace",
"path": "/orders/0",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
La operación move
- Si
path
apunta a un elemento de matriz: copia el elementofrom
en la ubicación del elementopath
y, luego, ejecuta una operaciónremove
en el elementofrom
. - Si
path
apunta a una propiedad: copia el valor de la propiedadfrom
en la propiedadpath
y, luego, ejecuta la operaciónremove
en la propiedadfrom
. - Si
path
apunta a una propiedad que no existe:- Si el recurso para revisar es un objeto estático: la solicitud produce un error.
- Si el recurso para revisar es un objeto dinámico: copia la propiedad
from
en la ubicación indicada porpath
y, luego, ejecuta una operaciónremove
en la propiedadfrom
.
En el siguiente documento de revisión de ejemplo:
- Se copia el valor de
Orders[0].OrderName
enCustomerName
. - Se establece
Orders[0].OrderName
en null. - Se mueve
Orders[1]
delante deOrders[0]
.
[
{
"op": "move",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "move",
"from": "/orders/1",
"path": "/orders/0"
}
]
La operación copy
Esta operación es funcionalmente igual que la operación move
sin el paso remove
final.
En el siguiente documento de revisión de ejemplo:
- Se copia el valor de
Orders[0].OrderName
enCustomerName
. - Se inserta una copia de
Orders[1]
delante deOrders[0]
.
[
{
"op": "copy",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "copy",
"from": "/orders/1",
"path": "/orders/0"
}
]
La operación test
Si el valor de la ubicación indicada por path
es diferente del valor proporcionado en value
, la solicitud produce un error. En ese caso, la solicitud PATCH entera produce un error incluso si todas las demás operaciones del documento de revisión se realizan correctamente.
La operación test
se usa habitualmente para impedir una actualización cuando hay un conflicto de simultaneidad.
El siguiente documento de revisión de ejemplo no tiene ningún efecto si el valor inicial de CustomerName
es "John", porque la prueba produce un error:
[
{
"op": "test",
"path": "/customerName",
"value": "Nancy"
},
{
"op": "add",
"path": "/customerName",
"value": "Barry"
}
]
Obtención del código
Vea o descargue el código de ejemplo. (Método de descarga).
Para probar el ejemplo, ejecute la aplicación y envíe solicitudes HTTP con la configuración siguiente:
- URL:
http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
- Método HTTP:
PATCH
- Encabezado:
Content-Type: application/json-patch+json
- Cuerpo: Copia y pega uno de los ejemplos de documento JSON Patch de la carpeta de proyecto JSON.
Recursos adicionales
- Especificación del método PATCH de IETF RFC 5789
- Especificación del método JSON PATCH de IETF RFC 6902
- IETF RFC 6901 JSON Pointer
- Documentación de JSON Patch. Incluye vínculos a recursos para crear documentos JSON Patch.
- Código fuente de JSON Patch para ASP.NET Core
En este artículo se explica cómo administrar solicitudes JSON Patch en una API web ASP.NET Core.
Instalación del paquete
Para habilitar la compatibilidad con JSON Patch en la aplicación, completa los siguientes pasos:
Instale el paquete NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson
.Actualice el método
Startup.ConfigureServices
del proyecto para llamar a AddNewtonsoftJson. Por ejemplo:services .AddControllersWithViews() .AddNewtonsoftJson();
AddNewtonsoftJson
es compatible con los métodos de registro del servicio MVC:
JSON Patch, AddNewtonsoftJson y System.Text.Json
AddNewtonsoftJson
reemplaza los formateadores de entrada y salida basados en System.Text.Json
, que se usan para dar formato a todo el contenido JSON. Para agregar compatibilidad con JSON Patch mediante Newtonsoft.Json
, sin cambiar los otros formateadores, actualiza el método Startup.ConfigureServices
del proyecto, como se indica a continuación:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.InputFormatters.Insert(0, GetJsonPatchInputFormatter());
});
}
private static NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter()
{
var builder = new ServiceCollection()
.AddLogging()
.AddMvc()
.AddNewtonsoftJson()
.Services.BuildServiceProvider();
return builder
.GetRequiredService<IOptions<MvcOptions>>()
.Value
.InputFormatters
.OfType<NewtonsoftJsonPatchInputFormatter>()
.First();
}
El código anterior requiere el paquete Microsoft.AspNetCore.Mvc.NewtonsoftJson
y las siguientes instrucciones using
:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using System.Linq;
Use el método Newtonsoft.Json.JsonConvert.SerializeObject
para serializar un JsonPatchDocument.
Método de solicitud HTTP PATCH
Los métodos PUT y PATCH se usan para actualizar un recurso existente. La diferencia entre ellos es que PUT reemplaza el recurso entero, mientras que PATCH especifica únicamente los cambios.
Revisión JSON
JSON Patch es un formato para especificar las actualizaciones que se aplicarán a un recurso. Un documento JSON Patch tiene una matriz de operaciones. Cada operación identifica un tipo determinado de cambio. Algunos ejemplos de estos cambios incluyen agregar un elemento de matriz o reemplazar un valor de propiedad.
Por ejemplo, los documentos JSON siguientes representan un recurso, un documento JSON Patch para el recurso y el resultado de aplicar las operaciones de actualización.
Ejemplo de recursos
{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}
Ejemplo de revisión de JSON
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
En el código JSON anterior:
- La propiedad
op
indica el tipo de operación. - La propiedad
path
indica el elemento que se va a actualizar. - La propiedad
value
proporciona el nuevo valor.
Recurso después de la revisión
Este es el recurso después de aplicar el documento JSON Patch anterior:
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
Los cambios realizados mediante la aplicación de un documento JSON Patch a un recurso son atómicos. Si cualquier operación en la lista falla, no se aplica ninguna operación de la lista.
Sintaxis de path
La propiedad path de un objeto de operación tiene barras inversas entre niveles. Por ejemplo, "/address/zipCode"
.
Para especificar elementos de matriz se usan índices de base cero. El primer elemento de la matriz addresses
estaría en /addresses/0
. Para usar add
al final de una matriz, use un guion (-
) en lugar de un número de índice: /addresses/-
.
Operations
En la siguiente tabla se muestran las operaciones admitidas, como se ha definido en la especificación de JSON Patch:
Operación | Notas |
---|---|
add |
Agrega un elemento de propiedad o matriz. Para la propiedad existente: establece el valor. |
remove |
Quita un elemento de propiedad o matriz. |
replace |
Lo mismo que remove seguido de add en la misma ubicación. |
move |
Lo mismo que remove desde el origen seguido de add al destino mediante el valor del origen. |
copy |
Lo mismo que add al destino mediante el valor del origen. |
test |
Devuelve el código de estado correcto si el valor en path = al value proporcionado. |
JSON Patch en ASP.NET Core
La implementación de ASP.NET Core de JSON Patch se proporciona en el paquete NuGet Microsoft.AspNetCore.JsonPatch.
Código del método de acción
En un controlador de API, un método de acción para JSON Patch:
- Se anota con el atributo
HttpPatch
. - Acepta
JsonPatchDocument<T>
, normalmente con[FromBody]
. - Llama a
ApplyTo
en el documento de revisión para aplicar los cambios.
Este es un ejemplo:
[HttpPatch]
public IActionResult JsonPatchWithModelState(
[FromBody] JsonPatchDocument<Customer> 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);
}
}
Este código de la aplicación de ejemplo funciona con el siguiente modelo Customer
:
using System.Collections.Generic;
namespace JsonPatchSample.Models
{
public class Customer
{
public string CustomerName { get; set; }
public List<Order> Orders { get; set; }
}
}
namespace JsonPatchSample.Models
{
public class Order
{
public string OrderName { get; set; }
public string OrderType { get; set; }
}
}
El método de acción de ejemplo:
- Construye un objeto
Customer
. - Aplica la revisión.
- Devuelve el resultado en el cuerpo de la respuesta.
En una aplicación real, el código recuperaría los datos de un almacén como una base de datos y actualizaría la base de datos después de aplicar la revisión.
Estado del modelo
En el ejemplo anterior del método de acción, se llama a una sobrecarga de ApplyTo
que toma el estado del modelo como uno de sus parámetros. Con esta opción, puede obtener mensajes de error en las respuestas. En el ejemplo siguiente se muestra el cuerpo de una respuesta 400 Solicitud incorrecta de una operación test
:
{
"Customer": [
"The current value 'John' at path 'customerName' is not equal to the test value 'Nancy'."
]
}
Objetos dinámicos
En el ejemplo siguiente de método de acción, se muestra cómo aplicar una revisión a un objeto dinámico:
[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
dynamic obj = new ExpandoObject();
patch.ApplyTo(obj);
return Ok(obj);
}
La operación add
- Si
path
apunta a un elemento de matriz: inserta un nuevo elemento delante del especificado porpath
. - Si
path
apunta a una propiedad: establece el valor de la propiedad. - Si
path
apunta a una ubicación que no existe:- Si el recurso para revisar es un objeto dinámico: agrega una propiedad.
- Si el recurso para revisar es un objeto estático: la solicitud produce un error.
El siguiente documento de revisión de ejemplo establece el valor de CustomerName
y agrega un objeto Order
al final de la matriz Orders
.
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
La operación remove
- Si
path
apunta a un elemento de matriz: quita el elemento. - Si
path
apunta a una propiedad:- Si el recurso para revisar es un objeto dinámico: quita la propiedad.
- Si el recurso para revisar es un objeto estático:
- Si la propiedad acepta valores NULL: la establece en null.
- Si la propiedad es distinta de null, la establece en
default<T>
.
En el siguiente documento de revisión de ejemplo, se establece CustomerName
en null y se elimina Orders[0]
:
[
{
"op": "remove",
"path": "/customerName"
},
{
"op": "remove",
"path": "/orders/0"
}
]
La operación replace
Esta operación es funcionalmente igual que remove
seguida de add
.
En el siguiente documento de revisión de ejemplo, se establece el valor de CustomerName
y se reemplaza Orders[0]
por un nuevo objeto Order
:
[
{
"op": "replace",
"path": "/customerName",
"value": "Barry"
},
{
"op": "replace",
"path": "/orders/0",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
La operación move
- Si
path
apunta a un elemento de matriz: copia el elementofrom
en la ubicación del elementopath
y, luego, ejecuta una operaciónremove
en el elementofrom
. - Si
path
apunta a una propiedad: copia el valor de la propiedadfrom
en la propiedadpath
y, luego, ejecuta la operaciónremove
en la propiedadfrom
. - Si
path
apunta a una propiedad que no existe:- Si el recurso para revisar es un objeto estático: la solicitud produce un error.
- Si el recurso para revisar es un objeto dinámico: copia la propiedad
from
en la ubicación indicada porpath
y, luego, ejecuta una operaciónremove
en la propiedadfrom
.
En el siguiente documento de revisión de ejemplo:
- Se copia el valor de
Orders[0].OrderName
enCustomerName
. - Se establece
Orders[0].OrderName
en null. - Se mueve
Orders[1]
delante deOrders[0]
.
[
{
"op": "move",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "move",
"from": "/orders/1",
"path": "/orders/0"
}
]
La operación copy
Esta operación es funcionalmente igual que la operación move
sin el paso remove
final.
En el siguiente documento de revisión de ejemplo:
- Se copia el valor de
Orders[0].OrderName
enCustomerName
. - Se inserta una copia de
Orders[1]
delante deOrders[0]
.
[
{
"op": "copy",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "copy",
"from": "/orders/1",
"path": "/orders/0"
}
]
La operación test
Si el valor de la ubicación indicada por path
es diferente del valor proporcionado en value
, la solicitud produce un error. En ese caso, la solicitud PATCH entera produce un error incluso si todas las demás operaciones del documento de revisión se realizan correctamente.
La operación test
se usa habitualmente para impedir una actualización cuando hay un conflicto de simultaneidad.
El siguiente documento de revisión de ejemplo no tiene ningún efecto si el valor inicial de CustomerName
es "John", porque la prueba produce un error:
[
{
"op": "test",
"path": "/customerName",
"value": "Nancy"
},
{
"op": "add",
"path": "/customerName",
"value": "Barry"
}
]
Obtención del código
Vea o descargue el código de ejemplo. (Método de descarga).
Para probar el ejemplo, ejecute la aplicación y envíe solicitudes HTTP con la configuración siguiente:
- URL:
http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
- Método HTTP:
PATCH
- Encabezado:
Content-Type: application/json-patch+json
- Cuerpo: Copia y pega uno de los ejemplos de documento JSON Patch de la carpeta de proyecto JSON.
Recursos adicionales
- Especificación del método PATCH de IETF RFC 5789
- Especificación del método JSON PATCH de IETF RFC 6902
- Especificación del formato de ruta de acceso JSON Patch de IETF RFC 6901
- Documentación de JSON Patch. Incluye vínculos a recursos para crear documentos JSON Patch.
- Código fuente de JSON Patch para ASP.NET Core