Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo explica como lidar com solicitações de patch JSON em uma API da Web ASP.NET Core.
O suporte ao patch JSON na API Web ASP.NET Core é baseado na System.Text.Json serialização e requer o Microsoft.AspNetCore.JsonPatch.SystemTextJson pacote NuGet.
O que é o padrão JSON Patch?
O padrão JSON Patch:
É um formato padrão para descrever 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:
addremovereplacemovecopytest
Em aplicativos Web, o Patch JSON é comumente 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 patches reduz o tamanho do payload e melhora a eficiência.
Para obter uma visão geral do padrão JSON Patch, consulte jsonpatch.com.
Suporte a patch JSON na API Web ASP.NET Core
O suporte ao patch JSON na API da Web ASP.NET Core é baseado na System.Text.Json serialização, começando com o .NET 10, implementando Microsoft.AspNetCore.JsonPatch com base na System.Text.Json serialização. Este recurso:
- Requer o
Microsoft.AspNetCore.JsonPatch.SystemTextJsonpacote NuGet. - Alinha-se com as práticas modernas do .NET aproveitando a System.Text.Json biblioteca, que é otimizada para .NET.
- Fornece melhor desempenho e uso reduzido de memória em comparação com a implementação baseada em
Newtonsoft.Jsonde legado. Para obter mais informações sobre a implementação baseada em herdadoNewtonsoft.Json, consulte a versão .NET 9 deste artigo.
Note
A implementação de Microsoft.AspNetCore.JsonPatch baseada em System.Text.Json não é um substituto imediato para a implementação baseada em Newtonsoft.Json. Não suporta tipos dinâmicos, por exemplo ExpandoObject.
Important
O padrão JSON Patch tem riscos de segurança inerentes. Como esses riscos são inerentes ao padrão JSON Patch, a implementação do ASP.NET Core não tenta mitigar os riscos de segurança inerentes. É responsabilidade do desenvolvedor garantir que o documento do patch JSON seja seguro para ser aplicado ao objeto de destino. Para obter mais informações, consulte a seção Mitigando riscos de segurança .
Habilite o suporte a patches JSON com System.Text.Json
Para habilitar o suporte ao 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 do 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 aplicando JSON Patch
Em um controlador de API, um método de ação para o patch JSON:
- É anotado com o atributo HttpPatchAttribute.
- Aceita um JsonPatchDocument<TModel>, normalmente com FromBodyAttribute.
- Chama ApplyTo(Object) no documento de patch para aplicar as alterações.
Exemplo de método de 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);
}
Este 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();
}
}
Principais etapas do método de ação de exemplo:
-
Recupere o cliente:
- O método recupera um
Customerobjeto do banco de dadosAppDbusando a id fornecida. - Se nenhum
Customerobjeto for encontrado, ele retornará uma404 Not Foundresposta.
- O método recupera um
-
Aplique o patch JSON:
- O ApplyTo(Object) método aplica as operações JSON Patch do patchDoc ao objeto recuperado
Customer. - Se ocorrerem erros durante o aplicativo de patch, como operações inválidas ou conflitos, eles serão capturados por um delegado de tratamento de erros. Este delegado adiciona mensagens de erro ao
ModelStateusando o nome do tipo do objeto afetado e a mensagem de erro.
- O ApplyTo(Object) método aplica as operações JSON Patch do patchDoc ao objeto recuperado
-
Validar ModelState:
- Depois de aplicar o patch, o método verifica
ModelStatepara erros. - Se o
ModelStatefor inválido, como devido a erros de patch, ele retornará uma400 Bad Requestresposta com os erros de validação.
- Depois de aplicar o patch, o método verifica
-
Retorne o cliente atualizado
- Se o patch for aplicado com êxito e o
ModelStatefor válido, o método retornará o objeto atualizadoCustomerna resposta.
- Se o patch for aplicado com êxito e o
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 a JsonPatchDocument<TModel> a um objeto
O exemplo a seguir demonstra:
- As operações
add,replaceeremove. - Operações em propriedades aninhadas.
- Adicionar um novo item a uma matriz.
- Usando um conversor JSON String Enum 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 processar o JsonPatchDocument<TModel>, incluindo o comportamento controlado pelas seguintes opções:
- JsonNumberHandling: Se as propriedades numéricas são lidas a partir de cadeias de caracteres.
- PropertyNameCaseInsensitive: Se os nomes de propriedade são sensíveis a maiúsculas e 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) são corrigidas.
- System.Text.Json A desserialização depende do tipo declarado para identificar 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.
JSON Patch suporta a test operação, que verifica se um valor especificado é igual à propriedade de destino. Se isso não acontecer, ele retornará um erro.
O exemplo a seguir demonstra como lidar com esses erros normalmente.
Important
O objeto passado para o ApplyTo(Object) método é modificado no local. O chamador é responsável por descartar as alterações caso alguma operação falhe.
// 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 de segurança
Ao usar o Microsoft.AspNetCore.JsonPatch.SystemTextJson pacote, é fundamental entender e mitigar 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.
Important
Esta não é uma lista exaustiva de ameaças. Os desenvolvedores de aplicativos devem conduzir suas próprias revisões de modelo de ameaça 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 JSON Patch em seus aplicativos, os desenvolvedores devem:
- Execute modelos de ameaças abrangentes para seus próprios aplicativos.
- Abordar as ameaças identificadas.
- Siga as atenuações recomendadas nas seções a seguir.
Negação de Serviço (DoS) via amplificação de memória
-
Cenário: Um cliente mal-intencionado envia uma
copyoperação que duplica gráficos de objetos grandes várias vezes, levando ao consumo excessivo de memória. - Impacto: condições potenciais de Out-Of-Memory (OOM), causando interrupções no serviço.
-
Mitigation:
- Valide os documentos de patch JSON recebidos quanto ao tamanho e estrutura antes de chamar ApplyTo(Object).
- A validação deve ser específica do aplicativo, mas um exemplo de validação pode ser semelhante ao 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 da lógica de negócios
- Cenário: as operações de patch podem manipular campos com invariantes implícitos (por exemplo, sinalizadores internos, IDs ou campos computados), violando restrições de negócios.
- Impacto: problemas de integridade de dados e comportamento não intencional do aplicativo.
-
Mitigation:
- Use POCOs (Plain Old CLR Objects) com propriedades explicitamente definidas que são seguras para modificar.
- Evite expor propriedades confidenciais ou críticas de segurança no objeto de destino.
- Se um objeto POCO não for usado, valide o objeto corrigido depois de aplicar operações para garantir que as regras de negócios e invariantes não sejam violadas.
- Use POCOs (Plain Old CLR Objects) com propriedades explicitamente definidas que são seguras para modificar.
Autenticação e autorização
- Cenário: Clientes não autenticados ou não autorizados enviam solicitações maliciosas de patch JSON.
- Impacto: acesso não autorizado para modificar dados confidenciais ou interromper o comportamento do aplicativo.
-
Mitigation:
- Proteja endpoints que aceitam solicitações de patch JSON com mecanismos de autenticação e autorização adequados.
- Restrinja o acesso a clientes ou usuários confiáveis com permissões apropriadas.
Obter o código
Ver e descarregar código de exemplo. (Como fazer o download).
Para testar o exemplo, execute o aplicativo e envie solicitações HTTP com as seguintes configurações:
- Endereço 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 do projeto JSON .
Recursos adicionais
Este artigo explica como lidar com solicitações de patch JSON em uma API da Web ASP.NET Core.
Important
O padrão JSON Patch tem riscos de segurança inerentes. Essa implementação não tenta mitigar esses riscos de segurança inerentes. É responsabilidade do desenvolvedor garantir que o documento do patch JSON seja seguro para ser aplicado 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 ASP.NET Core é baseado em Newtonsoft.Json e requer o pacote NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson.
Para habilitar o suporte ao patch JSON:
Instale o pacote NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson.Ligue para 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 de entrada e saída padrão baseados em System.Text.Jsonusados para formatar todo o conteúdo JSON . Esse método de extensão é compatível com os seguintes métodos de registro de serviço MVC:
JsonPatch requer a configuração do cabeçalho Content-Type para application/json-patch+json.
Adicionar suporte para JSON Patch ao usar System.Text.Json
O formatador de entrada baseado em System.Text.Jsonnão suporta o patch JSON. Para adicionar suporte para JSON Patch usando Newtonsoft.Json, deixando os outros formatadores de entrada e saída inalterados:
Instale o pacote NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson.Atualização
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. Esta ordem de registo garante que:
-
NewtonsoftJsonPatchInputFormatterprocessa solicitações de patch JSON. - As entradas e formatters existentes baseadas em
System.Text.Jsonprocessam 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 todo o recurso, enquanto PATCH especifica apenas as alterações.
JSON Patch (um padrão para manipulação de dados JSON)
JSON Patch é um formato para especificar atualizações a serem aplicadas a um recurso. Um documento de patch JSON tem uma matriz de operações . Cada operação identifica um tipo específico de alteração. Exemplos de tais 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 JSON Patch para o recurso e o resultado da aplicação das operações Patch.
Exemplo de recurso
{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}
Exemplo de patch JSON
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
No JSON anterior:
- A propriedade
opindica o tipo de operação. - A propriedade
pathindica o elemento a ser atualizado. - A propriedade
valuefornece o novo valor.
Recurso após patch
Aqui está o recurso depois de aplicar o documento de patch JSON 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 de patch JSON a um recurso são atômicas. Se alguma operação na lista falhar, nenhuma operação na lista será aplicada.
Sintaxe do caminho
A propriedade do caminho do objeto de operação tem barras entre níveis. Por exemplo, "/address/zipCode".
Os índices baseados em zero são usados para especificar elementos de matriz. O primeiro elemento da matriz addresses estaria em /addresses/0. Para add ao final de uma matriz, use um hífen (-) em vez de um número de índice: /addresses/-.
Operations
A tabela a seguir mostra as operações suportadas, conforme definido na especificação JSON Patch:
| Operation | Notes |
|---|---|
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 de add para o destino usando o valor da origem. |
copy |
O mesmo que add em direção ao destino, utilizando o valor da origem. |
test |
Retornar o código de status de sucesso se o valor em path for igual ao fornecido em value. |
JSON Patch no ASP.NET Core
A implementação ASP.NET Core do JSON Patch é fornecida no pacote Microsoft.AspNetCore.JsonPatch NuGet.
Código do método de ação
Em um controlador de API, um método de ação para o patch JSON:
- É anotado com o atributo
HttpPatch. - Aceita um JsonPatchDocument<TModel>, normalmente com
[FromBody]. - Chama ApplyTo(Object) no documento de patch para aplicar as alterações.
Aqui está 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);
}
}
Este código do aplicativo de exemplo funciona com o seguinte modelo de 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 método de exemplo da ação:
- Constrói um
Customer. - Aplica a correção.
- Devolve o resultado no corpo da resposta.
Em um aplicativo real, o código recuperaria os dados de um armazenamento, como um banco de dados, e atualizaria o banco de dados depois de aplicar o patch.
Estado do modelo
O exemplo de método de ação anterior invoca uma sobrecarga de ApplyTo que utiliza o estado do modelo como um dos 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 Bad Request para uma operação test:
{
"Customer": [
"The current value 'John' at path 'customerName' != test value 'Nancy'."
]
}
Objetos dinâmicos
O exemplo de 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 de adição
- Se
pathapontar para um elemento de matriz: insere novo elemento antes do especificado porpath. - Caso
pathaponte para uma propriedade: defini o valor da propriedade. - Se
pathapontar para um local inexistente:- Se o recurso a ser corrigido for um objeto dinâmico: adiciona uma propriedade.
- Se o recurso a ser corrigido for um objeto estático: a solicitação falhará.
O documento de patch de exemplo 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 de remoção
- Se
pathaponta para um elemento da matriz, remove o elemento. - Caso
pathaponte para uma propriedade:- Se o recurso a ser corrigido for um objeto dinâmico: remove a propriedade.
- Se o recurso a ser corrigido for um objeto estático:
- Se a propriedade for anulável: define-a como null.
- Se a propriedade não for anulável, define-a como
default<T>.
O seguinte documento de patch de exemplo define CustomerName como nulo e exclui Orders[0]:
[
{
"op": "remove",
"path": "/customerName"
},
{
"op": "remove",
"path": "/orders/0"
}
]
A operação de substituição
Esta operação é funcionalmente a mesma que um remove seguido por um add.
O documento de patch de exemplo 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 de mudança
- Se
pathapontar para um elemento de matriz: copia o elementofrompara o local do elementopathe, em seguida, executa uma operação deremoveno elementofrom. - Se
pathapontar para uma propriedade, copia o valor da propriedadefrompara a propriedadepathe, em seguida, executa uma operaçãoremovena propriedadefrom. - Se
pathapontar para um imóvel inexistente:- Se o recurso a ser corrigido for um objeto estático: a solicitação falhará.
- Se o recurso a ser corrigido for um objeto dinâmico: copia
frompropriedade para o local indicado porpathe, em seguida, executa uma operaçãoremovena propriedadefrom.
O seguinte documento de patch de exemplo:
- Copia o valor de
Orders[0].OrderNameparaCustomerName. - Define
Orders[0].OrderNamecomo null. - Move
Orders[1]para antes doOrders[0].
[
{
"op": "move",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "move",
"from": "/orders/1",
"path": "/orders/0"
}
]
A operação de cópia
Esta operação é funcionalmente a mesma que uma operação move sem a etapa final remove.
O seguinte documento de patch de exemplo:
- Copia o valor de
Orders[0].OrderNameparaCustomerName. - 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 de teste
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 PATCH falha, mesmo que todas as outras operações no documento de patch sejam bem-sucedidas.
A operação test é comumente usada para evitar uma atualização quando há um conflito de simultaneidade.
O seguinte documento de patch de exemplo não terá 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
Ver e descarregar código de exemplo. (Como fazer o download).
Para testar o exemplo, execute o aplicativo e envie solicitações HTTP com as seguintes configurações:
- Endereço 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 do projeto JSON .
Mitigação dos riscos de segurança
Ao usar o pacote Microsoft.AspNetCore.JsonPatch com a implementação baseada em Newtonsoft.Json, é fundamental entender e mitigar 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.
Important
Esta não é uma lista exaustiva de ameaças. Os desenvolvedores de aplicativos devem conduzir suas próprias revisões de modelo de ameaça 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 enquanto seguem as mitigações recomendadas abaixo, os consumidores desses pacotes podem integrar a funcionalidade JSON Patch em seus aplicativos, minimizando os riscos de segurança.
Negação de Serviço (DoS) via amplificação de memória
-
Cenário: Um cliente mal-intencionado envia uma
copyoperação que duplica gráficos de objetos grandes várias vezes, levando ao consumo excessivo de memória. - Impacto: condições potenciais de Out-Of-Memory (OOM), causando interrupções no serviço.
-
Mitigation:
- Valide os documentos de patch JSON recebidos quanto ao tamanho e estrutura antes de chamar
ApplyTo. - A validação precisa ser específica do aplicativo, mas um exemplo de validação pode ser semelhante ao seguinte:
- Valide os documentos de patch JSON recebidos 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 da lógica de negócios
- Cenário: as operações de patch podem manipular campos com invariantes implícitos (por exemplo, sinalizadores internos, IDs ou campos computados), violando restrições de negócios.
- Impacto: problemas de integridade de dados e comportamento não intencional do aplicativo.
-
Mitigation:
- Use objetos POCO com propriedades explicitamente definidas que são seguras para modificar.
- Evite expor propriedades confidenciais ou críticas de segurança no objeto de destino.
- Se nenhum objeto POCO for usado, valide o objeto corrigido depois de aplicar operações para garantir que as regras de negócios e invariantes não sejam violadas.
Autenticação e autorização
- Cenário: Clientes não autenticados ou não autorizados enviam solicitações maliciosas de patch JSON.
- Impacto: acesso não autorizado para modificar dados confidenciais ou interromper o comportamento do aplicativo.
-
Mitigation:
- Proteja endpoints que aceitam solicitações de patch JSON com mecanismos de autenticação e autorização adequados.
- Restrinja o acesso a clientes ou usuários confiáveis com permissões apropriadas.
Recursos adicionais
Este artigo explica como lidar com solicitações de patch JSON em uma API da Web ASP.NET Core.
Important
O padrão JSON Patch tem riscos de segurança inerentes. Como esses riscos são inerentes ao padrão JSON Patch, essa implementação não tenta mitigar os riscos de segurança inerentes. É responsabilidade do desenvolvedor garantir que o documento do patch JSON seja seguro para ser aplicado 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 em seu aplicativo, conclua as seguintes etapas:
Instale o pacote NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson.Atualize o método
Startup.ConfigureServicesdo 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 a entrada e saída baseadas em System.Text.Jsonpara assuntos usados para formatação todo o conteúdo JSON. Para adicionar suporte para JSON Patch usando Newtonsoft.Json, deixando os outros formatters inalterados, atualize o método Startup.ConfigureServices do projeto da seguinte maneira:
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 requer 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 todo o recurso, enquanto PATCH especifica apenas as alterações.
JSON Patch (um padrão para manipulação de dados JSON)
JSON Patch é um formato para especificar atualizações a serem aplicadas a um recurso. Um documento de patch JSON tem uma matriz de operações . Cada operação identifica um tipo específico de alteração. Exemplos de tais 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 JSON Patch para o recurso e o resultado da aplicação das operações Patch.
Exemplo de recurso
{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}
Exemplo de patch JSON
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
No JSON anterior:
- A propriedade
opindica o tipo de operação. - A propriedade
pathindica o elemento a ser atualizado. - A propriedade
valuefornece o novo valor.
Recurso após patch
Aqui está o recurso depois de aplicar o documento de patch JSON 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 de patch JSON a um recurso são atômicas. Se alguma operação na lista falhar, nenhuma operação na lista será aplicada.
Sintaxe do caminho
A propriedade do caminho do objeto de operação tem barras entre níveis. Por exemplo, "/address/zipCode".
Os índices baseados em zero são usados para especificar elementos de matriz. O primeiro elemento da matriz addresses estaria em /addresses/0. Para add ao final de uma matriz, use um hífen (-) em vez de um número de índice: /addresses/-.
Operations
A tabela a seguir mostra as operações suportadas, conforme definido na especificação JSON Patch:
| Operation | Notes |
|---|---|
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 de add para o destino usando o valor da origem. |
copy |
O mesmo que add em direção ao destino, utilizando o valor da origem. |
test |
Retornar o código de status de sucesso se o valor em path for igual ao fornecido em value. |
JSON Patch no ASP.NET Core
A implementação ASP.NET Core do JSON Patch é fornecida no pacote Microsoft.AspNetCore.JsonPatch NuGet.
Código do método de ação
Em um controlador de API, um método de ação para o patch JSON:
- É anotado com o atributo
HttpPatch. - Aceita um
JsonPatchDocument<T>, normalmente com[FromBody]. - Chama
ApplyTono documento de patch para aplicar as alterações.
Aqui está 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);
}
}
Este código do aplicativo de exemplo funciona com o seguinte modelo de 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 método de exemplo da ação:
- Constrói um
Customer. - Aplica a correção.
- Devolve o resultado no corpo da resposta.
Em um aplicativo real, o código recuperaria os dados de um armazenamento, como um banco de dados, e atualizaria o banco de dados depois de aplicar o patch.
Estado do modelo
O exemplo de método de ação anterior invoca uma sobrecarga de ApplyTo que utiliza o estado do modelo como um dos 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 Bad Request 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 de 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 de adição
- Se
pathapontar para um elemento de matriz: insere novo elemento antes do especificado porpath. - Caso
pathaponte para uma propriedade: defini o valor da propriedade. - Se
pathapontar para um local inexistente:- Se o recurso a ser corrigido for um objeto dinâmico: adiciona uma propriedade.
- Se o recurso a ser corrigido for um objeto estático: a solicitação falhará.
O documento de patch de exemplo 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 de remoção
- Se
pathaponta para um elemento da matriz, remove o elemento. - Caso
pathaponte para uma propriedade:- Se o recurso a ser corrigido for um objeto dinâmico: remove a propriedade.
- Se o recurso a ser corrigido for um objeto estático:
- Se a propriedade for anulável: define-a como null.
- Se a propriedade não for anulável, define-a como
default<T>.
O seguinte documento de patch de exemplo define CustomerName como nulo e exclui Orders[0]:
[
{
"op": "remove",
"path": "/customerName"
},
{
"op": "remove",
"path": "/orders/0"
}
]
A operação de substituição
Esta operação é funcionalmente a mesma que um remove seguido por um add.
O documento de patch de exemplo 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 de mudança
- Se
pathapontar para um elemento de matriz: copia o elementofrompara o local do elementopathe, em seguida, executa uma operação deremoveno elementofrom. - Se
pathapontar para uma propriedade, copia o valor da propriedadefrompara a propriedadepathe, em seguida, executa uma operaçãoremovena propriedadefrom. - Se
pathapontar para um imóvel inexistente:- Se o recurso a ser corrigido for um objeto estático: a solicitação falhará.
- Se o recurso a ser corrigido for um objeto dinâmico: copia
frompropriedade para o local indicado porpathe, em seguida, executa uma operaçãoremovena propriedadefrom.
O seguinte documento de patch de exemplo:
- Copia o valor de
Orders[0].OrderNameparaCustomerName. - Define
Orders[0].OrderNamecomo null. - Move
Orders[1]para antes doOrders[0].
[
{
"op": "move",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "move",
"from": "/orders/1",
"path": "/orders/0"
}
]
A operação de cópia
Esta operação é funcionalmente a mesma que uma operação move sem a etapa final remove.
O seguinte documento de patch de exemplo:
- Copia o valor de
Orders[0].OrderNameparaCustomerName. - 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 de teste
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 PATCH falha, mesmo que todas as outras operações no documento de patch sejam bem-sucedidas.
A operação test é comumente usada para evitar uma atualização quando há um conflito de simultaneidade.
O seguinte documento de patch de exemplo não terá 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
Ver e descarregar código de exemplo. (Como fazer o download).
Para testar o exemplo, execute o aplicativo e envie solicitações HTTP com as seguintes configurações:
- Endereço 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 do projeto JSON .