Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este artículo se explica cómo administrar solicitudes JSON Patch en una API web ASP.NET Core.
La compatibilidad con JSON Patch en la API web de ASP.NET Core se basa en la serialización de System.Text.Json y requiere el paquete NuGet Microsoft.AspNetCore.JsonPatch.SystemTextJson.
¿Qué es el estándar JSON Patch?
El estándar de parche JSON:
Es un formato estándar para describir los cambios que se aplicarán a un documento JSON.
Se define en RFC 6902 y se usa ampliamente en las API DE RESTful para realizar actualizaciones parciales en los recursos JSON.
Describe una secuencia de operaciones que modifican un documento JSON como:
addremovereplacemovecopytest
En las aplicaciones web, JSON Patch se utiliza comúnmente en una operación PATCH para realizar actualizaciones parciales de un recurso. En lugar de enviar todo el recurso para una actualización, los clientes pueden enviar un documento de revisión JSON que contenga solo los cambios. La aplicación de parches reduce el tamaño de la carga y mejora la eficiencia.
Para obtener información general sobre el estándar de revisión JSON, consulte jsonpatch.com.
Compatibilidad con JSON Patch en la API web de ASP.NET Core
El soporte de JSON Patch en la API web ASP.NET Core se basa en serialización System.Text.Json, a partir de .NET 10, implementando Microsoft.AspNetCore.JsonPatch basado en serialización System.Text.Json. Esta característica:
- Requiere el
Microsoft.AspNetCore.JsonPatch.SystemTextJsonpaquete NuGet. - Se alinea con las prácticas modernas de .NET aprovechando la System.Text.Json biblioteca, que está optimizada para .NET.
- Proporciona un rendimiento mejorado y un uso reducido de memoria en comparación con la implementación basada en la herencia
Newtonsoft.Json. Para obtener más información sobre la implementación basada en herenciaNewtonsoft.Json, consulte la versión de .NET 9 de este artículo.
Note
La implementación de Microsoft.AspNetCore.JsonPatch basada en la serialización de System.Text.Json no es un reemplazo directo para la implementación basada en Newtonsoft.Json heredada. No admite tipos dinámicos, por ejemplo ExpandoObject.
Important
El estándar json Patch tiene riesgos de seguridad inherentes. Dado que estos riesgos son inherentes al estándar de revisión JSON, la implementación de ASP.NET Core no intenta mitigar los riesgos de seguridad inherentes. Es responsabilidad del desarrollador asegurarse de que el documento de revisión JSON es seguro para aplicarlo al objeto de destino. Para obtener más información, consulte la sección Mitigación de riesgos de seguridad .
Habilitar compatibilidad con parches JSON con System.Text.Json
Para habilitar la compatibilidad con los parches JSON con System.Text.Json, instale el paquete NuGet Microsoft.AspNetCore.JsonPatch.SystemTextJson.
dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease
Este paquete proporciona una clase para representar un JsonPatchDocument<TModel> documento de revisión JSON para objetos de tipo T y lógica personalizada para serializar y deserializar documentos de revisión JSON mediante System.Text.Json. El método clave de la JsonPatchDocument<TModel> clase es ApplyTo(Object), que aplica las operaciones de revisión a un objeto de destino de tipo T.
Código del método de acción que aplica un parche JSON
En un controlador de API, un método de acción para JSON Patch:
- Se anota con el atributo HttpPatchAttribute.
- Acepta JsonPatchDocument<TModel>, normalmente con FromBodyAttribute.
- Llama a ApplyTo(Object) en el documento de revisión para aplicar los cambios.
Ejemplo de método de acción del controlador:
[HttpPatch("{id}", Name = "UpdateCustomer")]
public IActionResult Update(AppDb db, string id, [FromBody] JsonPatchDocument<Customer> 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);
}
Este código de la aplicación de ejemplo funciona con los siguientes Customer modelos y Order :
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<Order>? Orders { get; set; }
public Customer()
{
Id = Guid.NewGuid().ToString();
}
}
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();
}
}
Pasos clave del método de acción de ejemplo:
-
Recupere el cliente:
- El método recupera un
Customerobjeto de la base de datosAppDbmediante el identificador proporcionado. - Si no se encuentra ningún
Customerobjeto, devuelve una404 Not Foundrespuesta.
- El método recupera un
-
Aplicar JSON Patch:
- El método ApplyTo(Object) aplica las operaciones de parche JSON del patchDoc al objeto recuperado
Customer. - Si se producen errores durante la aplicación del parche, como operaciones o conflictos no válidos, se capturan mediante un delegado de control de errores. Este delegado agrega mensajes de error al
ModelStateutilizando el nombre de tipo del objeto afectado y el mensaje de error.
- El método ApplyTo(Object) aplica las operaciones de parche JSON del patchDoc al objeto recuperado
-
Validar ModelState:
- Después de aplicar el parche, el método comprueba el
ModelStateen busca de errores. - Si
ModelStatees inválido, debido a errores de parche, devuelve una respuesta400 Bad Requestcon los errores de validación.
- Después de aplicar el parche, el método comprueba el
-
Devuelve el cliente actualizado:
- Si la revisión se aplica correctamente y
ModelStatees válida, el método devuelve el objeto actualizadoCustomeren la respuesta.
- Si la revisión se aplica correctamente y
Respuesta de error de ejemplo:
En el ejemplo siguiente se muestra el cuerpo de una respuesta para una operación de parcheo JSON cuando la ruta especificada no es válida.
{
"Customer": [
"The target location specified by path segment 'foobar' was not found."
]
}
Aplicación de un documento de revisión JSON a un objeto
En los ejemplos siguientes se muestra cómo usar el ApplyTo(Object) método para aplicar un documento de revisión JSON a un objeto .
Ejemplo: Aplicar un JsonPatchDocument<TModel> a un objeto
En el ejemplo siguiente se muestra:
- Las operaciones
add,replace, yremove. - Operaciones sobre propiedades anidadas.
- Agregar un nuevo elemento a una matriz.
- Uso de un convertidor de enumeración de cadenas JSON en un documento de parche JSON.
// 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<JsonPatchDocument<Person>>(jsonPatch);
// Apply the JSON patch document
patchDoc!.ApplyTo(person);
// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));
El ejemplo anterior da como resultado la siguiente salida del objeto actualizado:
{
"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"
}
]
}
El método ApplyTo(Object) generalmente sigue las convenciones y opciones de System.Text.Json para procesar el JsonPatchDocument<TModel>, incluido el comportamiento controlado por las siguientes opciones:
- JsonNumberHandling: indica si las propiedades numéricas se leen de cadenas.
- PropertyNameCaseInsensitive: Si los nombres de las propiedades distinguen entre mayúsculas y minúsculas.
Diferencias clave entre System.Text.Json y la nueva JsonPatchDocument<TModel> implementación:
- El tipo en tiempo de ejecución del objeto destino, no el tipo declarado, determina qué propiedades ApplyTo(Object) parchea.
- System.Text.Json la deserialización se basa en el tipo declarado para identificar las propiedades elegibles.
Ejemplo: Aplicación de jsonPatchDocument con control de errores
Hay varios errores que pueden producirse al aplicar un documento de JSON Patch. Por ejemplo, es posible que el objeto de destino no tenga la propiedad especificada o que el valor especificado no sea compatible con el tipo de propiedad.
JSON Patch admite la test operación, que comprueba si un valor especificado es igual a la propiedad de destino. Si no es así, devuelve un error.
En el ejemplo siguiente se muestra cómo controlar estos errores correctamente.
Important
El objeto pasado al método ApplyTo(Object) se modifica en el lugar. El autor de la llamada es responsable de descartar los cambios si alguna operación falla.
// Original object
var person = new Person {
FirstName = "John",
LastName = "Doe",
Email = "johndoe@gmail.com"
};
// 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" }
]
""";
// Deserialize the JSON patch document
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);
// Apply the JSON patch document, catching any errors
Dictionary<string, string[]>? 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)}");
}
}
// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));
El ejemplo anterior da como resultado la siguiente salida:
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": []
}
Mitigación de riesgos de seguridad
Al usar el Microsoft.AspNetCore.JsonPatch.SystemTextJson paquete, es fundamental comprender y mitigar los posibles riesgos de seguridad. En las secciones siguientes se describen los riesgos de seguridad identificados asociados a la revisión JSON y se proporcionan mitigaciones recomendadas para garantizar el uso seguro del paquete.
Important
Esta no es una lista exhaustiva de amenazas. Los desarrolladores de aplicaciones deben llevar a cabo sus propias revisiones del modelo de amenazas para determinar una lista completa específica de la aplicación y elaborar mitigaciones adecuadas según sea necesario. Por ejemplo, las aplicaciones que exponen colecciones a operaciones de revisión deben tener en cuenta la posibilidad de ataques de complejidad algorítmica si esas operaciones insertan o quitan elementos al principio de la colección.
Para minimizar los riesgos de seguridad al integrar la funcionalidad JSON Patch en sus aplicaciones, los desarrolladores deben:
- Ejecute modelos de amenazas completos para sus propias aplicaciones.
- Abordar las amenazas identificadas.
- Siga las mitigaciones recomendadas en las secciones siguientes.
Denegación de servicio (DoS) a través de la amplificación de memoria
-
Escenario: un cliente malintencionado envía una
copyoperación que duplica gráficos de objetos grandes varias veces, lo que conduce a un consumo excesivo de memoria. - Impacto: Posibles condiciones Out-Of-Memory (OOM), causando interrupciones del servicio.
-
Mitigation:
- Valide los documentos de parche JSON entrantes en cuanto a tamaño y estructura antes de llamar a ApplyTo(Object).
- La validación debe ser específica de la aplicación, pero una validación de ejemplo puede ser similar a la siguiente:
public void Validate(JsonPatchDocument<T> 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();
}
}
Subversión de la lógica empresarial
- Escenario: las operaciones de parcheo pueden manipular campos con invariables implícitas (por ejemplo, marcas internas, identificadores o campos calculados), violando restricciones empresariales.
- Impacto: problemas de integridad de datos y comportamiento de la aplicación no deseado.
-
Mitigation:
- Use POCOs (Plain Old CLR Objects) con propiedades definidas explícitamente que resulten seguras de modificar.
- Evite exponer propiedades confidenciales o críticas para la seguridad en el objeto de destino.
- Si no se usa un objeto POCO, valide el objeto modificado después de aplicar operaciones, asegurándose de que no se infringen las reglas empresariales y las invariantes.
- Use POCOs (Plain Old CLR Objects) con propiedades definidas explícitamente que resulten seguras de modificar.
Autenticación y autorización
- Escenario: clientes no autenticados o no autorizados envían solicitudes maliciosas de parche JSON.
- Impacto: acceso no autorizado para modificar datos confidenciales o interrumpir el comportamiento de la aplicación.
-
Mitigation:
- Proteja los puntos de conexión que aceptan solicitudes de revisión JSON con mecanismos de autenticación y autorización adecuados.
- Restrinja el acceso a clientes o usuarios de confianza con los permisos adecuados.
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:
- Dirección URL:
http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate - Método HTTP:
PATCH - Encabezado:
Content-Type: application/json-patch+json - Cuerpo: Copie y pegue uno de los ejemplos de documento de revisión de JSON de la carpeta del proyecto JSON.
Recursos adicionales
En este artículo se explica cómo administrar solicitudes JSON Patch en una API web ASP.NET Core.
Important
El estándar json Patch tiene riesgos de seguridad inherentes. Esta implementación no intenta mitigar estos riesgos de seguridad inherentes. Es responsabilidad del desarrollador asegurarse de que el documento de revisión JSON es seguro para aplicarlo al objeto de destino. Para obtener más información, consulte la sección Mitigación de riesgos de seguridad .
Instalación de paquetes
La compatibilidad con JSON Patch en la API web ASP.NET Core se basa en Newtonsoft.Json y requiere el paquete Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet.
Para habilitar la compatibilidad con el 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 predeterminados basados en System.Text.Json utilizados para formatear 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.
Añadir compatibilidad con JSON Patch al utilizar System.Text.Json
El formateador de entrada basado en System.Text.Json no es compatible con JSON Patch. Para añadir compatibilidad con JSON Patch mediante Newtonsoft.Json, sin modificar 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:
-
NewtonsoftJsonPatchInputFormatterprocesa las solicitudes de JSON Patch. - Los formateadores de entrada y salida basados en
System.Text.Jsonprocesan 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 siguientes documentos JSON representan un recurso, un documento JSON Patch para el recurso y el resultado de aplicar las operaciones Patch.
Ejemplo de recurso
{
"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
opindica el tipo de operación. - La propiedad
pathindica el elemento que se va a actualizar. - La propiedad
valueproporciona 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 al aplicar 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 ruta de acceso
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:
| Operation | Notes |
|---|---|
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
pathapunta a un elemento de matriz: inserta un nuevo elemento delante del especificado porpath. - Si
pathapunta a una propiedad: establece el valor de la propiedad. - Si
pathapunta 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
pathapunta a un elemento de matriz: quita el elemento. - Si
pathapunta 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
pathapunta a un elemento de matriz: copia el elementofromen la ubicación del elementopathy, luego, ejecuta una operaciónremoveen el elementofrom. - Si
pathapunta a una propiedad: copia el valor de la propiedadfromen la propiedadpathy, luego, ejecuta la operaciónremoveen la propiedadfrom. - Si
pathapunta 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
fromen la ubicación indicada porpathy, luego, ejecuta una operaciónremoveen la propiedadfrom.
En el siguiente documento de revisión de ejemplo:
- Se copia el valor de
Orders[0].OrderNameenCustomerName. - Se establece
Orders[0].OrderNameen 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].OrderNameenCustomerName. - 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:
- Dirección URL:
http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate - Método HTTP:
PATCH - Encabezado:
Content-Type: application/json-patch+json - Cuerpo: Copie y pegue uno de los ejemplos de documento de revisión de JSON de la carpeta del proyecto JSON.
Mitigación de riesgos de seguridad
Al usar el paquete Microsoft.AspNetCore.JsonPatch con la implementación basada en Newtonsoft.Json, es fundamental comprender y mitigar los posibles riesgos de seguridad. En las secciones siguientes se describen los riesgos de seguridad identificados asociados a la revisión JSON y se proporcionan mitigaciones recomendadas para garantizar el uso seguro del paquete.
Important
Esta no es una lista exhaustiva de amenazas. Los desarrolladores de aplicaciones deben llevar a cabo sus propias revisiones del modelo de amenazas para determinar una lista completa específica de la aplicación y elaborar mitigaciones adecuadas según sea necesario. Por ejemplo, las aplicaciones que exponen colecciones a operaciones de revisión deben tener en cuenta la posibilidad de ataques de complejidad algorítmica si esas operaciones insertan o quitan elementos al principio de la colección.
Al ejecutar modelos de amenazas completos para sus propias aplicaciones y abordar las amenazas identificadas, mientras siguen las mitigaciones recomendadas a continuación, los consumidores de estos paquetes pueden integrar la funcionalidad JSON Patch en sus aplicaciones mientras minimizan los riesgos de seguridad.
Denegación de servicio (DoS) a través de la amplificación de memoria
-
Escenario: un cliente malintencionado envía una
copyoperación que duplica gráficos de objetos grandes varias veces, lo que conduce a un consumo excesivo de memoria. - Impacto: Posibles condiciones Out-Of-Memory (OOM), causando interrupciones del servicio.
-
Mitigation:
- Valide los documentos de parche JSON entrantes en cuanto a tamaño y estructura antes de llamar a
ApplyTo. - La validación debe ser específica de la aplicación, pero una validación de ejemplo puede ser similar a la siguiente:
- Valide los documentos de parche JSON entrantes en cuanto a tamaño y estructura antes de llamar a
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();
}
}
Subversión de la lógica empresarial
- Escenario: las operaciones de parcheo pueden manipular campos con invariables implícitas (por ejemplo, marcas internas, identificadores o campos calculados), violando restricciones empresariales.
- Impacto: problemas de integridad de datos y comportamiento de la aplicación no deseado.
-
Mitigation:
- Use objetos POCO con propiedades definidas explícitamente que sean seguras para modificar.
- Evite exponer propiedades confidenciales o críticas para la seguridad en el objeto de destino.
- Si no se usa ningún objeto POCO, valide el objeto parcheado después de aplicar las operaciones para asegurarse de que no se infringen las reglas de negocio y los invariantes.
Autenticación y autorización
- Escenario: clientes no autenticados o no autorizados envían solicitudes maliciosas de parche JSON.
- Impacto: acceso no autorizado para modificar datos confidenciales o interrumpir el comportamiento de la aplicación.
-
Mitigation:
- Proteja los puntos de conexión que aceptan solicitudes de revisión JSON con mecanismos de autenticación y autorización adecuados.
- Restrinja el acceso a clientes o usuarios de confianza con los permisos adecuados.
Recursos adicionales
En este artículo se explica cómo administrar solicitudes JSON Patch en una API web ASP.NET Core.
Important
El estándar json Patch tiene riesgos de seguridad inherentes. Dado que estos riesgos son inherentes al estándar de revisión JSON, esta implementación no intenta mitigar los riesgos de seguridad inherentes. Es responsabilidad del desarrollador asegurarse de que el documento de revisión JSON es seguro para aplicarlo al objeto de destino. Para obtener más información, consulte la sección Mitigación de riesgos de seguridad .
Instalación de paquetes
Para habilitar la compatibilidad con JSON Patch en su aplicación, siga estos pasos:
Instale el paquete NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson.Actualice el método
Startup.ConfigureServicesdel 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 añadir compatibilidad con JSON Patch utilizando Newtonsoft.Json, dejando los demás formateadores sin cambios, actualice 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 siguientes documentos JSON representan un recurso, un documento JSON Patch para el recurso y el resultado de aplicar las operaciones Patch.
Ejemplo de recurso
{
"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
opindica el tipo de operación. - La propiedad
pathindica el elemento que se va a actualizar. - La propiedad
valueproporciona 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 al aplicar 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 ruta de acceso
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:
| Operation | Notes |
|---|---|
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
ApplyToen 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
pathapunta a un elemento de matriz: inserta un nuevo elemento delante del especificado porpath. - Si
pathapunta a una propiedad: establece el valor de la propiedad. - Si
pathapunta 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
pathapunta a un elemento de matriz: quita el elemento. - Si
pathapunta 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
pathapunta a un elemento de matriz: copia el elementofromen la ubicación del elementopathy, luego, ejecuta una operaciónremoveen el elementofrom. - Si
pathapunta a una propiedad: copia el valor de la propiedadfromen la propiedadpathy, luego, ejecuta la operaciónremoveen la propiedadfrom. - Si
pathapunta 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
fromen la ubicación indicada porpathy, luego, ejecuta una operaciónremoveen la propiedadfrom.
En el siguiente documento de revisión de ejemplo:
- Se copia el valor de
Orders[0].OrderNameenCustomerName. - Se establece
Orders[0].OrderNameen 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].OrderNameenCustomerName. - 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:
- Dirección URL:
http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate - Método HTTP:
PATCH - Encabezado:
Content-Type: application/json-patch+json - Cuerpo: Copie y pegue uno de los ejemplos de documento de revisión de JSON de la carpeta del proyecto JSON.