JsonPatch na API Web do ASP.NET Core
Este artigo explica como lidar com solicitações de JSON Patch em uma API Web do ASP.NET Core.
Instalação do pacote
O suporte ao patch JSON na API Web do ASP.NET Core é baseado em Newtonsoft.Json
e precisa do pacote NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson
. Para habilitar o suporte ao patch JSON:
Instale o pacote do NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson
.Chame AddNewtonsoftJson. Por exemplo:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers() .AddNewtonsoftJson(); var app = builder.Build(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
AddNewtonsoftJson
substitui os formatadores padrão de entrada e saída baseados em System.Text.Json
usados para formatar todo o conteúdo JSON. Esse método de extensão é compatível com os seguintes métodos de registro do serviço MVC:
O JsonPatch exige a configuração do cabeçalho Content-Type
como application/json-patch+json
.
Adicionar suporte ao patch JSON ao usar System.Text.Json
O formatador de entrada baseado em System.Text.Json
não suporta o patch JSON. Para adicionar suporte ao patch JSON usando Newtonsoft.Json
, deixando inalterados os outros formatadores de entrada e saída:
Instale o pacote do NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson
.Atualizar
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(); } }
O código anterior cria uma instância de NewtonsoftJsonPatchInputFormatter e a insere como a primeira entrada na coleção MvcOptions.InputFormatters. Essa ordem de registro assegura que:
NewtonsoftJsonPatchInputFormatter
processa solicitações patch JSON.- A entrada e os formatadores baseados em
System.Text.Json
existentes processam todas as outras solicitações e respostas JSON.
Use o método Newtonsoft.Json.JsonConvert.SerializeObject
para serializar um JsonPatchDocument.
Método de solicitação HTTP PATCH
Os métodos PUT e PATCH são usados para atualizar um recurso existente. A diferença entre eles é que PUT substitui o recurso inteiro, enquanto PATCH especifica apenas as alterações.
Patch JSON
JSON Patch é um formato para especificar as atualizações a serem aplicadas a um recurso. Um documento de JSON Patch tem uma matriz de operações. Cada operação identifica um tipo específico de alteração. Exemplos dessas alterações incluem a adição de um elemento de matriz ou a substituição de um valor de propriedade.
Por exemplo, os documentos JSON a seguir representam um recurso, um documento de patch JSON para o recurso e o resultado da aplicação de operações patch.
Exemplo de recurso
{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}
Exemplo de JSON Patch
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
No JSON anterior:
- A propriedade
op
indica o tipo de operação. - A propriedade
path
indica o elemento a ser atualizado. - A propriedade
value
fornece o novo valor.
Recurso depois do patch
Este é o recurso após a aplicação do documento JSON Patch anterior:
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
As alterações feitas pela aplicação de um documento patch JSON a um recurso são atômicas. Se alguma operação da lista falhar, nenhuma operação da lista será aplicada.
Sintaxe de path
A propriedade path de um objeto de operação tem barras entre os níveis. Por exemplo, "/address/zipCode"
.
Índices baseados em zero são usados para especificar os elementos da matriz. O primeiro elemento da matriz addresses
estaria em /addresses/0
. Para add
até o final de uma matriz, use um hífen (-
) em vez de um número de índice: /addresses/-
.
Operações
A tabela a seguir mostra operações compatíveis conforme definido na especificação de JSON Patch:
Operação | Observações |
---|---|
add |
Adicione uma propriedade ou elemento de matriz. Para a propriedade existente: defina o valor. |
remove |
Remova uma propriedade ou elemento de matriz. |
replace |
É o mesmo que remove , seguido por add no mesmo local. |
move |
É o mesmo que remove da origem, seguido por add ao destino usando um valor da origem. |
copy |
É o mesmo que add ao destino usando um valor da origem. |
test |
Retorna o código de status de êxito se o valor em path é igual ao value fornecido. |
Patch JSON em ASP.NET Core
A implementação do ASP.NET Core de JSON Patch é fornecida no pacote do NuGet Microsoft.AspNetCore.JsonPatch.
Código do método de ação
Em um controlador de API, um método de ação para JSON Patch:
- É anotado com o atributo
HttpPatch
. - Aceita um JsonPatchDocument<TModel>, normalmente com
[FromBody]
. - Chama ApplyTo(Object) no documento de patch para aplicar as alterações.
Veja um exemplo:
[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);
}
}
Esse código do aplicativo de exemplo funciona com o seguinte 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; }
}
O exemplo de método de ação:
- Constrói um
Customer
. - Aplica o patch.
- Retorna o resultado no corpo da resposta.
Em um aplicativo real, o código recuperaria os dados de um repositório, como um banco de dados, e atualizaria o banco de dados após a aplicação do patch.
Estado do modelo
O exemplo de método de ação anterior chama uma sobrecarga de ApplyTo
que utiliza o estado do modelo como um de seus parâmetros. Com essa opção, você pode receber mensagens de erro nas respostas. O exemplo a seguir mostra o corpo de uma resposta 400 Solicitação Incorreta para uma operação test
:
{
"Customer": [
"The current value 'John' at path 'customerName' != test value 'Nancy'."
]
}
Objetos dinâmicos
O exemplo do método de ação a seguir mostra como aplicar um patch a um objeto dinâmico:
[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
dynamic obj = new ExpandoObject();
patch.ApplyTo(obj);
return Ok(obj);
}
A operação add
- Se
path
aponta para um elemento de matriz: insere um novo elemento antes do especificado porpath
. - Se
path
aponta para uma propriedade: define o valor da propriedade. - Se
path
aponta para um local não existente:- Se o recurso no qual fazer patch é um objeto dinâmico: adiciona uma propriedade.
- Se o recurso no qual fazer patch é um objeto estático: a solicitação falha.
O exemplo de documento de patch a seguir define o valor de CustomerName
e adiciona um objeto Order
ao final da matriz Orders
.
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
A operação remove
- Se
path
aponta para um elemento de matriz: remove o elemento. - Se
path
aponta para uma propriedade:- Se o recurso no qual fazer patch é um objeto dinâmico: remove a propriedade.
- Se o recurso no qual fazer patch é um objeto estático:
- Se a propriedade é anulável: define como nulo.
- Se a propriedade não é anulável: define como
default<T>
.
O seguinte exemplo de documento de patch define CustomerName
como nulo e exclui Orders[0]
:
[
{
"op": "remove",
"path": "/customerName"
},
{
"op": "remove",
"path": "/orders/0"
}
]
A operação replace
Esta operação é funcionalmente a mesma que remove
seguida por add
.
O exemplo de documento de patch a seguir define o valor de CustomerName
e substitui Orders[0]
por um novo objeto Order
:
[
{
"op": "replace",
"path": "/customerName",
"value": "Barry"
},
{
"op": "replace",
"path": "/orders/0",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
A operação move
- Se
path
aponta para um elemento de matriz: copia o elementofrom
para o local do elementopath
e, em seguida, executa uma operaçãoremove
no elementofrom
. - Se
path
aponta para uma propriedade: copia o valor da propriedadefrom
para a propriedadepath
, depois executa uma operaçãoremove
na propriedadefrom
. - Se
path
aponta para uma propriedade não existente:- Se o recurso no qual fazer patch é um objeto estático: a solicitação falha.
- Se o recurso no qual fazer patch é um objeto dinâmico: copia a propriedade
from
para o local indicado porpath
e, em seguida, executa uma operaçãoremove
na propriedadefrom
.
O seguinte exemplo de documento de patch:
- Copia o valor de
Orders[0].OrderName
paraCustomerName
. - Define
Orders[0].OrderName
como nulo. - Move
Orders[1]
para antes deOrders[0]
.
[
{
"op": "move",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "move",
"from": "/orders/1",
"path": "/orders/0"
}
]
A operação copy
Esta operação é funcionalmente a mesma que uma operação move
, sem a etapa final remove
.
O seguinte exemplo de documento de patch:
- Copia o valor de
Orders[0].OrderName
paraCustomerName
. - Insere uma cópia de
Orders[1]
antes deOrders[0]
.
[
{
"op": "copy",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "copy",
"from": "/orders/1",
"path": "/orders/0"
}
]
A operação test
Se o valor no local indicado por path
for diferente do valor fornecido em value
, a solicitação falhará. Nesse caso, toda a solicitação de PATCH falhará, mesmo se todas as outras operações no documento de patch forem bem-sucedidas.
A operação test
normalmente é usada para impedir uma atualização quando há um conflito de simultaneidade.
O seguinte exemplo de documento de patch não terá nenhum efeito se o valor inicial de CustomerName
for "John", porque o teste falha:
[
{
"op": "test",
"path": "/customerName",
"value": "Nancy"
},
{
"op": "add",
"path": "/customerName",
"value": "Barry"
}
]
Obter o código
Exibir ou baixar o código de exemplo. (Como baixar.)
Para testar o exemplo, execute o aplicativo e envie solicitações HTTP com as seguintes configurações:
- URL:
http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
- Método HTTP:
PATCH
- Cabeçalho:
Content-Type: application/json-patch+json
- Corpo: copie e cole um dos exemplos de documento de patch JSON da pasta de projeto JSON.
Recursos adicionais
- Especificação do método PATCH IETF RFC 5789
- Especificação do JSON Patch IETF RFC 6902
- Pointer JSON IETF RFC 6901
- Documentação do JSON Patch. Inclui links para recursos a fim de criar documentos de JSON Patch.
- Código-fonte de JSON Patch do ASP.NET Core
Este artigo explica como lidar com solicitações de JSON Patch em uma API Web do ASP.NET Core.
Instalação do pacote
Para habilitar o suporte ao patch JSON no seu aplicativo, conclua as etapas a seguir:
Instale o pacote do NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson
.Atualize o método
Startup.ConfigureServices
do projeto para chamar AddNewtonsoftJson. Por exemplo:services .AddControllersWithViews() .AddNewtonsoftJson();
AddNewtonsoftJson
é compatível com os métodos de registro do serviço MVC:
Patch JSON, AddNewtonsoftJson e System.Text.Json
AddNewtonsoftJson
substitui os formatadores de entrada e saída baseados em System.Text.Json
usados para formatar todo o conteúdo JSON. Para adicionar suporte ao patch JSON usando Newtonsoft.Json
sem alterar os outros formatadores, atualize o método Startup.ConfigureServices
do projeto da seguinte forma:
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();
}
O código anterior exige o pacote Microsoft.AspNetCore.Mvc.NewtonsoftJson
e as seguintes instruções 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 o método Newtonsoft.Json.JsonConvert.SerializeObject
para serializar um JsonPatchDocument.
Método de solicitação HTTP PATCH
Os métodos PUT e PATCH são usados para atualizar um recurso existente. A diferença entre eles é que PUT substitui o recurso inteiro, enquanto PATCH especifica apenas as alterações.
Patch JSON
JSON Patch é um formato para especificar as atualizações a serem aplicadas a um recurso. Um documento de JSON Patch tem uma matriz de operações. Cada operação identifica um tipo específico de alteração. Exemplos dessas alterações incluem a adição de um elemento de matriz ou a substituição de um valor de propriedade.
Por exemplo, os documentos JSON a seguir representam um recurso, um documento de patch JSON para o recurso e o resultado da aplicação de operações patch.
Exemplo de recurso
{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}
Exemplo de JSON Patch
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
No JSON anterior:
- A propriedade
op
indica o tipo de operação. - A propriedade
path
indica o elemento a ser atualizado. - A propriedade
value
fornece o novo valor.
Recurso depois do patch
Este é o recurso após a aplicação do documento JSON Patch anterior:
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
As alterações feitas pela aplicação de um documento patch JSON a um recurso são atômicas. Se alguma operação da lista falhar, nenhuma operação da lista será aplicada.
Sintaxe de path
A propriedade path de um objeto de operação tem barras entre os níveis. Por exemplo, "/address/zipCode"
.
Índices baseados em zero são usados para especificar os elementos da matriz. O primeiro elemento da matriz addresses
estaria em /addresses/0
. Para add
até o final de uma matriz, use um hífen (-
) em vez de um número de índice: /addresses/-
.
Operações
A tabela a seguir mostra operações compatíveis conforme definido na especificação de JSON Patch:
Operação | Observações |
---|---|
add |
Adicione uma propriedade ou elemento de matriz. Para a propriedade existente: defina o valor. |
remove |
Remova uma propriedade ou elemento de matriz. |
replace |
É o mesmo que remove , seguido por add no mesmo local. |
move |
É o mesmo que remove da origem, seguido por add ao destino usando um valor da origem. |
copy |
É o mesmo que add ao destino usando um valor da origem. |
test |
Retorna o código de status de êxito se o valor em path é igual ao value fornecido. |
Patch JSON em ASP.NET Core
A implementação do ASP.NET Core de JSON Patch é fornecida no pacote do NuGet Microsoft.AspNetCore.JsonPatch.
Código do método de ação
Em um controlador de API, um método de ação para JSON Patch:
- É anotado com o atributo
HttpPatch
. - Aceita um
JsonPatchDocument<T>
, normalmente com[FromBody]
. - Chama
ApplyTo
no documento de patch para aplicar as alterações.
Veja um exemplo:
[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);
}
}
Esse código do aplicativo de exemplo funciona com o seguinte 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; }
}
}
O exemplo de método de ação:
- Constrói um
Customer
. - Aplica o patch.
- Retorna o resultado no corpo da resposta.
Em um aplicativo real, o código recuperaria os dados de um repositório, como um banco de dados, e atualizaria o banco de dados após a aplicação do patch.
Estado do modelo
O exemplo de método de ação anterior chama uma sobrecarga de ApplyTo
que utiliza o estado do modelo como um de seus parâmetros. Com essa opção, você pode receber mensagens de erro nas respostas. O exemplo a seguir mostra o corpo de uma resposta 400 Solicitação Incorreta para uma operação test
:
{
"Customer": [
"The current value 'John' at path 'customerName' is not equal to the test value 'Nancy'."
]
}
Objetos dinâmicos
O exemplo do método de ação a seguir mostra como aplicar um patch a um objeto dinâmico:
[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
dynamic obj = new ExpandoObject();
patch.ApplyTo(obj);
return Ok(obj);
}
A operação add
- Se
path
aponta para um elemento de matriz: insere um novo elemento antes do especificado porpath
. - Se
path
aponta para uma propriedade: define o valor da propriedade. - Se
path
aponta para um local não existente:- Se o recurso no qual fazer patch é um objeto dinâmico: adiciona uma propriedade.
- Se o recurso no qual fazer patch é um objeto estático: a solicitação falha.
O exemplo de documento de patch a seguir define o valor de CustomerName
e adiciona um objeto Order
ao final da matriz Orders
.
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
A operação remove
- Se
path
aponta para um elemento de matriz: remove o elemento. - Se
path
aponta para uma propriedade:- Se o recurso no qual fazer patch é um objeto dinâmico: remove a propriedade.
- Se o recurso no qual fazer patch é um objeto estático:
- Se a propriedade é anulável: define como nulo.
- Se a propriedade não é anulável: define como
default<T>
.
O seguinte exemplo de documento de patch define CustomerName
como nulo e exclui Orders[0]
:
[
{
"op": "remove",
"path": "/customerName"
},
{
"op": "remove",
"path": "/orders/0"
}
]
A operação replace
Esta operação é funcionalmente a mesma que remove
seguida por add
.
O exemplo de documento de patch a seguir define o valor de CustomerName
e substitui Orders[0]
por um novo objeto Order
:
[
{
"op": "replace",
"path": "/customerName",
"value": "Barry"
},
{
"op": "replace",
"path": "/orders/0",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
A operação move
- Se
path
aponta para um elemento de matriz: copia o elementofrom
para o local do elementopath
e, em seguida, executa uma operaçãoremove
no elementofrom
. - Se
path
aponta para uma propriedade: copia o valor da propriedadefrom
para a propriedadepath
, depois executa uma operaçãoremove
na propriedadefrom
. - Se
path
aponta para uma propriedade não existente:- Se o recurso no qual fazer patch é um objeto estático: a solicitação falha.
- Se o recurso no qual fazer patch é um objeto dinâmico: copia a propriedade
from
para o local indicado porpath
e, em seguida, executa uma operaçãoremove
na propriedadefrom
.
O seguinte exemplo de documento de patch:
- Copia o valor de
Orders[0].OrderName
paraCustomerName
. - Define
Orders[0].OrderName
como nulo. - Move
Orders[1]
para antes deOrders[0]
.
[
{
"op": "move",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "move",
"from": "/orders/1",
"path": "/orders/0"
}
]
A operação copy
Esta operação é funcionalmente a mesma que uma operação move
, sem a etapa final remove
.
O seguinte exemplo de documento de patch:
- Copia o valor de
Orders[0].OrderName
paraCustomerName
. - Insere uma cópia de
Orders[1]
antes deOrders[0]
.
[
{
"op": "copy",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "copy",
"from": "/orders/1",
"path": "/orders/0"
}
]
A operação test
Se o valor no local indicado por path
for diferente do valor fornecido em value
, a solicitação falhará. Nesse caso, toda a solicitação de PATCH falhará, mesmo se todas as outras operações no documento de patch forem bem-sucedidas.
A operação test
normalmente é usada para impedir uma atualização quando há um conflito de simultaneidade.
O seguinte exemplo de documento de patch não terá nenhum efeito se o valor inicial de CustomerName
for "John", porque o teste falha:
[
{
"op": "test",
"path": "/customerName",
"value": "Nancy"
},
{
"op": "add",
"path": "/customerName",
"value": "Barry"
}
]
Obter o código
Exibir ou baixar o código de exemplo. (Como baixar.)
Para testar o exemplo, execute o aplicativo e envie solicitações HTTP com as seguintes configurações:
- URL:
http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
- Método HTTP:
PATCH
- Cabeçalho:
Content-Type: application/json-patch+json
- Corpo: copie e cole um dos exemplos de documento de patch JSON da pasta de projeto JSON.
Recursos adicionais
- Especificação do método PATCH IETF RFC 5789
- Especificação do JSON Patch IETF RFC 6902
- Especificação de formato de caminho do JSON Patch IETF RFC 6901
- Documentação do JSON Patch. Inclui links para recursos a fim de criar documentos de JSON Patch.
- Código-fonte de JSON Patch do ASP.NET Core