JsonPatch na API Web do ASP.NET Core
Este artigo explica como lidar com solicitações JSON Patch em uma API Web do ASP.NET Core.
Instalação do pacote
JSO suporte ao ON Patch na API Web do ASP.NET Core é baseado em Newtonsoft.Json
e exige o pacote NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson
. Para habilitar o suporte ao JSON Patch:
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 todoJSo conteúdo ON. 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 JSON Patch ao usar System.Text.Json
O formatador de entrada baseado em System.Text.Json
não suporta JSON Patch. Para adicionar suporte ao JSON Patch 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 JSsolicitações ON Patch.- 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.
JSON Patch
JSON Patch é um formato para especificar as atualizações a serem aplicadas a um recurso. Um documento 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 seguintes JSdocumentos ON representam um recurso, um JSdocumento ON Patch para o recurso e o resultado da aplicação das operações de Patch.
Exemplo de recurso
{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}
JSExemplo de ON 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
Aqui está o recurso após a aplicação do JSdocumento ON 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 JSdocumento ON Patch 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 as operações suportadas, conforme definido na especificação 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. |
JSON Patch no ASP.NET Core
A implementação do ASP.NET Core do JSON Patch é fornecida no pacote 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 JSdocumento ON Patch da pasta do projeto JSON.
Recursos adicionais
- Especificação do método PATCH IETF RFC 5789
- Especificação do ON Patch JSIETF RFC 6902
- ON Pointer JSIETF RFC 6901
- JSDocumentação do ON Patch. Inclui links para recursos para criar JSdocumentos ON Patch.
- Código-fonte do ON Patch JSASP.NET Core
Este artigo explica como lidar com solicitações JSON Patch em uma API Web do ASP.NET Core.
Instalação do pacote
Para habilitar o suporte ao JSON Patch 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:
JSON Patch, AddNewtonsoftJson e System.Text.Json
AddNewtonsoftJson
substitui os formatadores de entrada e saída baseados em System.Text.Json
usados para formatar todoJSo o conteúdo ON. Para adicionar suporte ao JSON Patch usando Newtonsoft.Json
, deixando os outros formatadores inalterados, 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.
JSON Patch
JSON Patch é um formato para especificar as atualizações a serem aplicadas a um recurso. Um documento 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 seguintes JSdocumentos ON representam um recurso, um JSdocumento ON Patch para o recurso e o resultado da aplicação das operações de Patch.
Exemplo de recurso
{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}
JSExemplo de ON 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
Aqui está o recurso após a aplicação do JSdocumento ON 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 JSdocumento ON Patch 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 as operações suportadas, conforme definido na especificação 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. |
JSON Patch no ASP.NET Core
A implementação do ASP.NET Core do JSON Patch é fornecida no pacote 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 JSdocumento ON Patch da pasta do projeto JSON.
Recursos adicionais
- Especificação do método PATCH IETF RFC 5789
- Especificação do ON Patch JSIETF RFC 6902
- especificação do formato do caminho ON Patch JSIETF RFC 6901
- JSDocumentação do ON Patch. Inclui links para recursos para criar JSdocumentos ON Patch.
- Código-fonte do ON Patch JSASP.NET Core
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de