Por actualización parcial me refiero a la actualización de algunas campos o propiedades de un objeto. La actualización por PUT reemplaza toda la información escribiendo campos vacíos cuando no se pasa contenido. Es decir, PUT actualiza el recurso pasado en su totalidad mientras que PATCH solamente actualiza los campos pasados en la llamada.
Con PUT se necesita pasar todos los campos que el recurso contenga, de otra forma los campos no pasados se consideran vacíos o nulos.
PATCH es útil para actualizar un campo sin tener que hacer un GET antes, ahorrando así una llamada, pero no quiere decir que sea una recomendación que tenga que utilizarse siempre ya que hay que recordar que todo depende del Contexto.
El método PATCH es particular. Es un método que consta de otras operaciones según la forma de actualización parcial que queramos, así pues tenemos las siguientes operaciones ('op'):
add, remove, replace, copy, move, test
P.ej.
{ "op": "replace", "path": "/helados/1/sabor", "value": "Chocolate" }
{ "op": "add", "path": "/helados/2", "value": { "name": "Dracula Chocolate" } }
{ "op": "remove", "path": "/helados/0" }
{ "op": "replace", "path": "/helados/1/sabor", "value": "Vainilla" }
{ "op": "copy", "from": "/helados/1", "path": "/superhelado" }
{ "op": "move", "from": "/helados", "path": "/mejoreshelados" }
{ "op": "test", "path": "/helados/name", "value": "Vainilla" } //para saber si existe
Estos ejemplos están escritos en formato JSON y es la forma con la que se trabajará con el
Nuget Asp.Net Core JsonPatch (que debemos añadir a nuestro proyecto)
Existen otras librerías como Ramone, JsonPatch, Starcounter, Nancy.JsonPatch, Manatee.Json
1) Creamos nuestro método en ASP.NET Core
[HttpPatch("{id:int}")]
[EnableCors("MiPoliticaCORS")]
public async Task<IActionResult> Patch(int id, [FromBody]JsonPatchDocument<Video> patch)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Video videoBBDD = _context.Videos.AsNoTracking().SingleOrDefault(c => c.Id == id);
if (videoBBDD == null) return NotFound();
if (id != videoBBDD.Id)
{
return BadRequest();
}
if (!VideoExists(id))
{
return NotFound();
}
patch.ApplyTo(videoBBDD, ModelState); //Importante este paso
try
{
_context.Videos.Update(videoBBDD);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
....
}
return Ok();
}
Destacar:
- En la declaración de argumentos utilizamos JsonPatchDocument<Video>
- Usar el método .ApplyTo()
En este caso quería actualizar un campo dado en el formato JSON que se
ha visto en una fila de una BBDD de videos.
2) Pruebas del método PATCH con jQuery
(sí, ya sé que puedo hacerlo con Angular, XMLHttpRequest(), etc, etc. )
var sendJsonData = [{
"op": "replace", "path": "/name", "value": "valorCambioConPatch"
}];
console.log("JSON.stringify:", JSON.stringify(sendJsonData));
$.ajax({
type: 'PATCH',
contentType: "application/json; charset=utf-8",
headers: [{ 'Access-Control-Allow-Origin': 'http://localhost' },
{ 'Access-Control-Allow-Methods': 'PATCH' } ],
url: "http://...../api/videos/1",
dataType: "json",
data: JSON.stringify(sendJsonData),
success: function (response) {
console.log('Success: ', response);
},
error: function (jqXHR, textStatus, errorThrown){
console.log("textStatus:",textStatus);
console.log("errorThrown:",errorThrown);
}
}).done(function hecho(){
console.log('Hecho.');
});
Destacar:
- Importantísimo que en este caso sendJsonData sea un array. Hay que pasar un Array,
no me funcionó hasta que no lo contemplé.
- 'op' es la operación a realizar como hemos visto, en este caso quiero cambiar un valor
por otro pues utilizo replace
- 'path', no es una ruta de un sistema de ficheros, sino el campo del objeto BBDD que quiero cambiar (también me ha funcionado sin slash '/'
pero en todos los sitios que he leído lo incluye)
- 'value', es el nuevo valor que quiero establecer
Referencias y páginas que me han ayudado:
- JsonPatch.com
- http://benfoster.io/blog/aspnet-core-json-patch-partial-api-updates
- https://michael-mckenna.com/how-to-add-json-patch-support-to-asp-net-web-api/
- https://kimsereyblog.blogspot.com.es/2017/11/implement-patch-on-asp-net-core-with.html
- https://docs.angularjs.org/api/ng/service/$http#patch
Notas finales
"PATCH is neither safe nor idempotent." (https://tools.ietf.org/html/rfc5789#section-9.1)
es decir, PATCH no es seguro, entiendiendo como no seguro el tratamiento de colisiones
al acceder al mismo tiempo sobre un mismo recurso
Collisions from multiple PATCH requests may be more dangerous than
PUT collisions because some patch formats need to operate from a
known base-point or else they will corrupt the resource.
Y continua...
"Clients using this kind of patch application SHOULD use a conditional request
such that the request will fail if the resource has been updated
since the client last accessed the resource."
"...The server MUST apply the entire set of changes atomically and never
provide (e.g., in response to a GET during this operation) a
partially modified representation."
Luego se sigue comentando problemas con el caché, para sentenciar ...
"There is no guarantee that a resource can be modified with PATCH..."
"Clients need to choose when to use PATCH rather than PUT."
Así pues, y después de lo leído, antes de utilizar PATCH hay que saber qué se hace.
Más referencias:
http://wahlnetwork.com/2015/07/13/patch/
https://tools.ietf.org/html/rfc5789#section-9.1