Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo explica como lidar com solicitações de JSON Patch em uma API Web do ASP.NET Core.
O suporte a JSON Patch na API Web do ASP.NET Core é baseado na serialização System.Text.Json e requer o pacote NuGet Microsoft.AspNetCore.JsonPatch.SystemTextJson
.
O que é o padrão de patch JSON?
O padrão de patch JSON:
É um formato padrão para descrever as alterações a serem aplicadas a um documento JSON.
É definido no RFC 6902 e é amplamente usado em APIs RESTful para executar atualizações parciais para recursos JSON.
Descreve uma sequência de operações que modificam um documento JSON, como:
add
remove
replace
move
copy
test
Em aplicativos Web, o Patch JSON geralmente é usado em uma operação PATCH para executar atualizações parciais de um recurso. Em vez de enviar todo o recurso para uma atualização, os clientes podem enviar um documento de patch JSON contendo apenas as alterações. A aplicação de patch reduz o tamanho da carga e melhora a eficiência.
Para obter uma visão geral do padrão de Patch JSON, consulte jsonpatch.com.
Suporte a patch JSON na API Web do ASP.NET Core
O suporte ao JSON Patch na API Web do ASP.NET Core baseia-se na serialização System.Text.Json, começando com o .NET 10, implementando Microsoft.AspNetCore.JsonPatch com base na serialização System.Text.Json. Este recurso:
- Requer o
Microsoft.AspNetCore.JsonPatch.SystemTextJson
pacote NuGet. - Alinha-se às práticas modernas do .NET aproveitando a System.Text.Json biblioteca, que é otimizada para .NET.
- Fornece desempenho aprimorado e consumo de memória reduzido em comparação com a implementação baseada em legado
Newtonsoft.Json
. Para obter mais informações sobre a implementação baseada em legadoNewtonsoft.Json
, consulte a versão do .NET 9 deste artigo.
Observação
A implementação de Microsoft.AspNetCore.JsonPatch baseada na serialização System.Text.Json não é uma substituição direta para a implementação antiga baseada em Newtonsoft.Json
. Ele não dá suporte a tipos dinâmicos, por exemplo ExpandoObject.
Importante
O padrão de patch JSON tem riscos de segurança inerentes. Como esses riscos são inerentes ao padrão de Patch JSON, a implementação do ASP.NET Core não tenta reduzir os riscos inerentes à segurança. É responsabilidade do desenvolvedor garantir que o documento do Patch JSON seja seguro para aplicar ao objeto de destino. Para obter mais informações, consulte a seção Mitigando riscos de segurança .
Habilitar o suporte a patch JSON com System.Text.Json
Para habilitar o suporte a JSON Patch com System.Text.Json, instale o pacote NuGet Microsoft.AspNetCore.JsonPatch.SystemTextJson
.
dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease
Este pacote fornece uma JsonPatchDocument<TModel> classe para representar um documento de Patch JSON para objetos de tipo T
e lógica personalizada para serializar e desserializar documentos de Patch JSON usando System.Text.Json. O método chave da JsonPatchDocument<TModel> classe é ApplyTo(Object), que aplica as operações de patch a um objeto de destino do tipo T
.
Código do método de ação que aplica o patch JSON
Em um controlador de API, um método de ação para JSON Patch:
- É anotado com o atributo HttpPatchAttribute.
- Aceita um JsonPatchDocument<TModel>, normalmente com FromBodyAttribute.
- Chama ApplyTo(Object) no documento de patch para aplicar as alterações.
Método de exemplo da ação do 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);
}
Esse código do aplicativo de exemplo funciona com o seguinte Customer
e Order
modelos:
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();
}
}
As principais etapas do método de ação de exemplo:
- Recuperar o Cliente:
- O método recupera um
Customer
objeto do banco de dadosAppDb
usando a ID fornecida. - Se nenhum
Customer
objeto for encontrado, ele retornará uma404 Not Found
resposta.
- O método recupera um
- Aplicar patch JSON:
- O ApplyTo(Object) método aplica as operações de patch JSON do patchDoc ao objeto recuperado
Customer
. - Se ocorrerem erros durante o aplicativo de patch, como operações ou conflitos inválidos, eles serão capturados por um delegado de tratamento de erros. Esse delegado adiciona mensagens de erro ao
ModelState
usando o nome do tipo do objeto afetado e a mensagem de erro.
- O ApplyTo(Object) método aplica as operações de patch JSON do patchDoc ao objeto recuperado
- Valide ModelState:
- Depois de aplicar o patch, o método verifica se há erros em
ModelState
. - Se o
ModelState
valor for inválido, como devido a erros de patch, ele retornará uma400 Bad Request
resposta com os erros de validação.
- Depois de aplicar o patch, o método verifica se há erros em
- Retorne o cliente atualizado:
- Se o patch for aplicado com êxito e
ModelState
for válido, o método retornará o objeto atualizadoCustomer
na resposta.
- Se o patch for aplicado com êxito e
Exemplo de resposta de erro:
O exemplo a seguir mostra o corpo de uma 400 Bad Request
resposta para uma operação de patch JSON quando o caminho especificado é inválido:
{
"Customer": [
"The target location specified by path segment 'foobar' was not found."
]
}
Aplicar um documento de patch JSON a um objeto
Os exemplos a seguir demonstram como usar o ApplyTo(Object) método para aplicar um documento de Patch JSON a um objeto.
Exemplo: aplicar um JsonPatchDocument<TModel> a um objeto
O exemplo a seguir demonstra:
- As operações
add
,replace
eremove
. - Operações em propriedades aninhadas.
- Adicionando um novo item a uma matriz.
- Usando um conversor de enumeração de cadeia de caracteres JSON em um documento de patch 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));
O exemplo anterior resulta na seguinte saída do objeto atualizado:
{
"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"
}
]
}
O ApplyTo(Object) método geralmente segue as convenções e opções de System.Text.Json para processamento do JsonPatchDocument<TModel>, incluindo o comportamento controlado pelas seguintes opções:
- JsonNumberHandling: se as propriedades numéricas são lidas de cadeias de caracteres.
- PropertyNameCaseInsensitive: se os nomes de propriedades diferenciam maiúsculas de minúsculas.
Principais diferenças entre System.Text.Json e a nova JsonPatchDocument<TModel> implementação:
- O tipo de tempo de execução do objeto de destino, não o tipo declarado, determina quais propriedades ApplyTo(Object) altera.
- A desserialização System.Text.Json depende do tipo declarado para identificar as propriedades elegíveis.
Exemplo: aplicar um JsonPatchDocument com tratamento de erros
Há vários erros que podem ocorrer ao aplicar um documento de patch JSON. Por exemplo, o objeto de destino pode não ter a propriedade especificada ou o valor especificado pode ser incompatível com o tipo de propriedade.
O JSON Patch
dá suporte à test
operação, que verifica se um valor especificado é igual à propriedade de destino. Se isso não ocorrer, retornará um erro.
O exemplo a seguir demonstra como lidar com esses erros normalmente.
Importante
O objeto passado para o ApplyTo(Object) método é modificado no local. O chamador é responsável por descartar alterações se alguma operação falhar.
// 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));
O exemplo anterior resulta na seguinte saída:
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": []
}
Mitigação dos riscos à segurança
Ao usar o Microsoft.AspNetCore.JsonPatch.SystemTextJson
pacote, é essencial entender e atenuar possíveis riscos de segurança. As seções a seguir descrevem os riscos de segurança identificados associados ao Patch JSON e fornecem mitigações recomendadas para garantir o uso seguro do pacote.
Importante
Essa não é uma lista completa de ameaças. Os desenvolvedores de aplicativos devem realizar suas próprias revisões de modelo de ameaças para determinar uma lista abrangente específica do aplicativo e criar mitigações apropriadas, conforme necessário. Por exemplo, os aplicativos que expõem coleções a operações de patch devem considerar o potencial de ataques de complexidade algorítmica se essas operações inserirem ou removerem elementos no início da coleção.
Para minimizar os riscos de segurança ao integrar a funcionalidade de Patch JSON em seus aplicativos, os desenvolvedores devem:
- Execute modelos de ameaças abrangentes para seus próprios aplicativos.
- Abordar ameaças identificadas.
- Siga as mitigações recomendadas nas seções a seguir.
Negação de Serviço (DoS) por meio da amplificação de memória
- Cenário: um cliente mal-intencionado envia uma
copy
operação que duplica grafos de objetos grandes várias vezes, levando ao consumo excessivo de memória. - Impacto: possíveis condições de Out-Of-Memory (OOM), causando interrupções no serviço.
- Mitigação:
- Valide os documentos JSON Patch de entrada quanto ao tamanho e à estrutura antes de chamar ApplyTo(Object).
- A validação deve ser específica do aplicativo, mas uma validação de exemplo pode ser semelhante à seguinte:
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();
}
}
Subversão lógica de negócios
- Cenário: as operações de patch podem manipular campos com invariáveis implícitas (por exemplo, sinalizadores internos, IDs ou campos computados), violando restrições comerciais.
- Impacto: problemas de integridade de dados e comportamento de aplicativo não intencional.
- Mitigação:
- Use POCOs (Objetos CLR Antigos Simples) com propriedades definidas explicitamente que são seguras de modificar.
- Evite expor propriedades confidenciais ou críticas à segurança no objeto de destino.
- Se um objeto POCO não for usado, valide o objeto corrigido após a aplicação de operações para garantir que regras de negócios e invariáveis não sejam violados.
- Use POCOs (Objetos CLR Antigos Simples) com propriedades definidas explicitamente que são seguras de modificar.
Autenticação e autorização
- Cenário: clientes não autenticados ou não autorizados enviam solicitações de patch JSON mal-intencionadas.
- Impacto: acesso não autorizado para modificar dados confidenciais ou interromper o comportamento do aplicativo.
- Mitigação:
- Proteja os endpoints que aceitam solicitações JSON Patch com mecanismos de autenticação e autorização adequados.
- Restrinja o acesso a clientes confiáveis ou usuários com permissões apropriadas.
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 JSON Patch da pasta de projeto JSON.
Recursos adicionais
Este artigo explica como lidar com solicitações de JSON Patch em uma API Web do ASP.NET Core.
Importante
O padrão de patch JSON tem riscos de segurança inerentes. Essa implementação não tenta atenuar esses riscos inerentes à segurança. É responsabilidade do desenvolvedor garantir que o documento do Patch JSON seja seguro para aplicar ao objeto de destino. Para obter mais informações, consulte a seção Mitigando riscos de segurança .
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 dá suporte ao patch JSON. Para adicionar suporte ao patch JSON usando Newtonsoft.Json
, enquanto os outros formatadores de entrada e saída permanecem inalterados:
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.
JSON Patch
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 seguintes documentos JSON representam um recurso, um documento de JSON Patch 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 ao aplicar 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 JSON Patch da pasta de projeto JSON.
Mitigação dos riscos à segurança
Ao usar o pacote Microsoft.AspNetCore.JsonPatch
com a implementação baseada em Newtonsoft.Json
, é crítico entender e atenuar possíveis riscos de segurança. As seções a seguir descrevem os riscos de segurança identificados associados ao Patch JSON e fornecem mitigações recomendadas para garantir o uso seguro do pacote.
Importante
Essa não é uma lista completa de ameaças. Os desenvolvedores de aplicativos devem realizar suas próprias revisões de modelo de ameaças para determinar uma lista abrangente específica do aplicativo e criar mitigações apropriadas, conforme necessário. Por exemplo, os aplicativos que expõem coleções a operações de patch devem considerar o potencial de ataques de complexidade algorítmica se essas operações inserirem ou removerem elementos no início da coleção.
Ao executar modelos de ameaças abrangentes para seus próprios aplicativos e lidar com ameaças identificadas, seguindo as mitigações recomendadas abaixo, os consumidores desses pacotes podem integrar a funcionalidade do Patch JSON em seus aplicativos, minimizando os riscos de segurança.
Negação de Serviço (DoS) por meio da amplificação de memória
- Cenário: um cliente mal-intencionado envia uma
copy
operação que duplica grafos de objetos grandes várias vezes, levando ao consumo excessivo de memória. - Impacto: possíveis condições de Out-Of-Memory (OOM), causando interrupções no serviço.
- Mitigação:
- Valide os documentos JSON Patch de entrada quanto ao tamanho e à estrutura antes de chamar
ApplyTo
. - A validação precisa ser específica do aplicativo, mas uma validação de exemplo pode ser semelhante à seguinte:
- Valide os documentos JSON Patch de entrada quanto ao tamanho e à estrutura antes de chamar
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();
}
}
Subversão lógica de negócios
- Cenário: as operações de patch podem manipular campos com invariáveis implícitas (por exemplo, sinalizadores internos, IDs ou campos computados), violando restrições comerciais.
- Impacto: problemas de integridade de dados e comportamento de aplicativo não intencional.
- Mitigação:
- Use objetos POCO com propriedades definidas explicitamente que são seguras de modificar.
- Evite expor propriedades confidenciais ou críticas à segurança no objeto de destino.
- Se nenhum objeto POCO for usado, valide o objeto corrigido após a aplicação de operações para garantir que regras de negócios e invariáveis não sejam violados.
Autenticação e autorização
- Cenário: clientes não autenticados ou não autorizados enviam solicitações de patch JSON mal-intencionadas.
- Impacto: acesso não autorizado para modificar dados confidenciais ou interromper o comportamento do aplicativo.
- Mitigação:
- Proteja os endpoints que aceitam solicitações JSON Patch com mecanismos de autenticação e autorização adequados.
- Restrinja o acesso a clientes confiáveis ou usuários com permissões apropriadas.
Recursos adicionais
Este artigo explica como lidar com solicitações de JSON Patch em uma API Web do ASP.NET Core.
Importante
O padrão de patch JSON tem riscos de segurança inerentes. Como esses riscos são inerentes ao padrão de Patch JSON, essa implementação não tenta reduzir os riscos inerentes à segurança. É responsabilidade do desenvolvedor garantir que o documento do Patch JSON seja seguro para aplicar ao objeto de destino. Para obter mais informações, consulte a seção Mitigando riscos de segurança .
Instalação do pacote
Para habilitar o suporte ao patch JSON no seu aplicativo, conclua as seguintes etapas:
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 padrão 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.
JSON Patch
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 seguintes documentos JSON representam um recurso, um documento de JSON Patch 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 ao aplicar 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 JSON Patch da pasta de projeto JSON.