Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questo articolo descrive come gestire le richieste JSON Patch in un'API Web ASP.NET Core.
Il supporto delle patch JSON nell'API Web di ASP.NET Core si basa sulla System.Text.Json serializzazione e richiede il Microsoft.AspNetCore.JsonPatch.SystemTextJson pacchetto NuGet.
Che cos'è lo standard JSON Patch?
Lo standard JSON Patch:
Formato standard per descrivere le modifiche da applicare a un documento JSON.
È definito in RFC 6902 ed è ampiamente usato nelle API RESTful per eseguire aggiornamenti parziali alle risorse JSON.
Descrive una sequenza di operazioni che modificano un documento JSON, ad esempio:
addremovereplacemovecopytest
Nelle app Web, patch JSON viene comunemente usata in un'operazione PATCH per eseguire aggiornamenti parziali di una risorsa. Anziché inviare l'intera risorsa per un aggiornamento, i client possono inviare un documento patch JSON contenente solo le modifiche. L'applicazione di patch riduce le dimensioni del payload e migliora l'efficienza.
Per una panoramica dello standard JSON Patch, vedere jsonpatch.com.
Supporto di patch JSON nell'API Web di ASP.NET Core
Il supporto per le JSON Patch nell'API Web di ASP.NET Core è basato sulla serializzazione System.Text.Json, e a partire da .NET 10, implementa Microsoft.AspNetCore.JsonPatch in base alla serializzazione System.Text.Json. Questa funzionalità offre i vantaggi seguenti:
- Richiede il
Microsoft.AspNetCore.JsonPatch.SystemTextJsonpacchetto NuGet. - È in linea con le procedure .NET moderne sfruttando la System.Text.Json libreria, ottimizzata per .NET.
- Offre prestazioni migliorate e un utilizzo ridotto della memoria rispetto all'implementazione basata su legacy
Newtonsoft.Json. Per altre informazioni sull'implementazione basata su legacyNewtonsoft.Json, vedere la versione .NET 9 di questo articolo.
Note
L'implementazione di Microsoft.AspNetCore.JsonPatch basata sulla serializzazione System.Text.Json non può sostituire l'implementazione basata su Newtonsoft.Json legacy. Non supporta i tipi dinamici, ad esempio ExpandoObject.
Important
Lo standard JSON Patch presenta rischi di sicurezza intrinseci. Poiché questi rischi sono intrinseci allo standard JSON Patch, l'implementazione di ASP.NET Core non tenta di attenuare i rischi di sicurezza intrinseci. È responsabilità dello sviluppatore assicurarsi che il documento JSON Patch sia sicuro da applicare all'oggetto di destinazione. Per altre informazioni, vedere la sezione Mitigazione dei rischi per la sicurezza .
Abilitare il supporto delle patch JSON con System.Text.Json
Per abilitare il supporto delle patch JSON con System.Text.Json, installare il Microsoft.AspNetCore.JsonPatch.SystemTextJson pacchetto NuGet.
dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease
Questo pacchetto fornisce una JsonPatchDocument<TModel> classe per rappresentare un documento JSON Patch per oggetti di tipo T e logica personalizzata per la serializzazione e la deserializzazione di documenti patch JSON tramite System.Text.Json. Il metodo chiave della JsonPatchDocument<TModel> classe è ApplyTo(Object), che applica le operazioni patch a un oggetto di destinazione di tipo T.
Codice del metodo di azione che applica patch JSON
In un controller API un metodo di azione per JSON Patch:
- Viene annotato con l'attributo HttpPatchAttribute.
- Accetta un oggetto JsonPatchDocument<TModel>, in genere con FromBodyAttribute.
- Chiama ApplyTo(Object) nel documento di patch per applicare le modifiche.
Esempio di metodo di azione del controller:
[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);
}
Questo codice dell'app di esempio funziona con i modelli Customer e Order seguenti:
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();
}
}
Passaggi chiave del metodo di azione di esempio:
-
Recuperare il cliente:
- Il metodo recupera un
Customeroggetto dal databaseAppDbutilizzando l'ID specificato. - Se non viene trovato alcun
Customeroggetto, restituisce una404 Not Foundrisposta.
- Il metodo recupera un
-
Applica patch JSON:
- Il metodo ApplyTo(Object) applica le operazioni di JSON Patch dal documento di patch al
Customeroggetto recuperato. - Se si verificano errori durante l'applicazione patch, ad esempio operazioni o conflitti non validi, vengono acquisiti da un delegato di gestione degli errori. Questo delegato aggiunge messaggi di errore all'oggetto
ModelStateutilizzando il nome del tipo dell'oggetto interessato e il messaggio di errore.
- Il metodo ApplyTo(Object) applica le operazioni di JSON Patch dal documento di patch al
-
Validate ModelState:
- Dopo aver applicato la patch, il metodo controlla
ModelStateper errori. - Se l'oggetto
ModelStatenon è valido, ad esempio a causa di errori di patch, restituisce una400 Bad Requestrisposta con gli errori di convalida.
- Dopo aver applicato la patch, il metodo controlla
-
Restituire il cliente aggiornato:
- Se la patch viene applicata correttamente e l'oggetto
ModelStateè valido, il metodo restituisce l'oggetto aggiornatoCustomernella risposta.
- Se la patch viene applicata correttamente e l'oggetto
Risposta di errore di esempio:
L'esempio seguente mostra il corpo di una risposta 400 Bad Request per un'operazione JSON Patch quando il percorso specificato non è valido.
{
"Customer": [
"The target location specified by path segment 'foobar' was not found."
]
}
Applicare un documento Patch JSON a un oggetto
Gli esempi seguenti illustrano come usare il ApplyTo(Object) metodo per applicare un documento Patch JSON a un oggetto .
Esempio: Applicare un JsonPatchDocument<TModel> a un oggetto
L'esempio seguente illustra:
- Operazioni
add,replaceeremove. - Operazioni sulle proprietà annidate.
- Aggiunta di un nuovo elemento a una matrice.
- Uso di un convertitore di enumerazioni di stringhe JSON in un documento di 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));
L'esempio precedente restituisce l'output seguente dell'oggetto aggiornato:
{
"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"
}
]
}
Il metodo ApplyTo(Object) segue in genere le convenzioni e le opzioni di System.Text.Json per l'elaborazione di JsonPatchDocument<TModel>, incluso il comportamento controllato dalle seguenti opzioni:
- JsonNumberHandling: indica se le proprietà numeriche vengono lette dalle stringhe.
- PropertyNameCaseInsensitive: indica se i nomi delle proprietà fanno distinzione tra maiuscole e minuscole.
Differenze principali tra System.Text.Json e la nuova JsonPatchDocument<TModel> implementazione:
- Il tipo di runtime dell'oggetto di destinazione, non il tipo dichiarato, determina quali proprietà vengono modificate da ApplyTo(Object).
- System.Text.Json la deserializzazione si basa sul tipo dichiarato per identificare le proprietà idonee.
Esempio: Applicare un jsonPatchDocument con la gestione degli errori
Esistono diversi errori che possono verificarsi quando si applica un documento patch JSON. Ad esempio, l'oggetto di destinazione potrebbe non avere la proprietà specificata oppure il valore specificato potrebbe non essere compatibile con il tipo di proprietà.
JSON Patch supporta l'operazione test , che controlla se un valore specificato è uguale alla proprietà di destinazione. In caso contrario, restituisce un errore.
Nell'esempio seguente viene illustrato come gestire correttamente questi errori.
Important
L'oggetto passato al metodo ApplyTo(Object) viene modificato sul posto. Il chiamante è responsabile dell'eliminazione delle modifiche in caso di errore di un'operazione.
// 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));
L'esempio precedente restituisce l'output seguente:
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": []
}
Prevenzione dei rischi per la sicurezza
Quando si usa il Microsoft.AspNetCore.JsonPatch.SystemTextJson pacchetto, è fondamentale comprendere e mitigare i potenziali rischi per la sicurezza. Le sezioni seguenti illustrano i rischi di sicurezza identificati associati alla patch JSON e forniscono mitigazioni consigliate per garantire l'utilizzo sicuro del pacchetto.
Important
Questo non è un elenco completo delle minacce. Gli sviluppatori di app devono condurre revisioni del proprio modello di minaccia per determinare un elenco completo specifico dell'app e trovare soluzioni di mitigazione appropriate in base alle esigenze. Ad esempio, le app che espongono raccolte alle operazioni patch devono considerare il potenziale di attacchi di complessità algoritmica se tali operazioni inseriscono o rimuovono elementi all'inizio della raccolta.
Per ridurre al minimo i rischi di sicurezza durante l'integrazione della funzionalità Patch JSON nelle app, gli sviluppatori devono:
- Eseguire modelli di minaccia completi per le proprie app.
- Risolvere le minacce identificate.
- Seguire le mitigazioni consigliate nelle sezioni seguenti.
Denial of Service (DoS) tramite amplificazione della memoria
-
Scenario: un client dannoso invia un'operazione
copyche duplica più volte grafici di oggetti di grandi dimensioni, causando un consumo eccessivo di memoria. - Impatto: potenziali condizioni di out-Of-Memory (OOM), causando interruzioni del servizio.
-
Mitigation:
- Convalidare i documenti patch JSON in ingresso per le dimensioni e la struttura prima di chiamare ApplyTo(Object).
- La convalida deve essere specifica dell'app, ma una convalida di esempio può essere simile alla seguente:
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();
}
}
Subversione della logica di business
- Scenario: le operazioni patch possono modificare i campi con invarianti impliciti (ad esempio flag interni, ID o campi calcolati), violando i vincoli aziendali.
- Impatto: problemi di integrità dei dati e comportamento imprevisto dell'app.
-
Mitigation:
- Usare oggetti POCO (Plain Old CLR Objects) con proprietà definite in modo esplicito che sono sicure da modificare.
- Evitare di esporre proprietà sensibili o critiche per la sicurezza nell'oggetto di destinazione.
- Se non viene usato un oggetto POCO, convalidare l'oggetto modificato dopo l'applicazione delle operazioni per garantire che le regole aziendali e le invarianti non vengano violate.
- Usare oggetti POCO (Plain Old CLR Objects) con proprietà definite in modo esplicito che sono sicure da modificare.
Autenticazione e autorizzazione
- Scenario: i client non autenticati o non autorizzati inviano richieste di patch JSON dannose.
- Impatto: accesso non autorizzato per modificare i dati sensibili o interrompere il comportamento dell'app.
-
Mitigation:
- Proteggere gli endpoint che accettano richieste patch JSON con meccanismi di autenticazione e autorizzazione appropriati.
- Limitare l'accesso a client o utenti attendibili con autorizzazioni appropriate.
Ottenere il codice
Visualizzare o scaricare il codice di esempio. (Come scaricare un esempio).
Per testare l'esempio, eseguire l'app e inviare richieste HTTP con le impostazioni seguenti:
- URL:
http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate - Metodo HTTP:
PATCH - Intestazione:
Content-Type: application/json-patch+json - Corpo: copiare e incollare uno degli esempi di documenti patch JSON dalla cartella del progetto JSON .
Risorse aggiuntive
Questo articolo descrive come gestire le richieste JSON Patch in un'API Web ASP.NET Core.
Important
Lo standard JSON Patch presenta rischi di sicurezza intrinseci. Questa implementazione non tenta di attenuare questi rischi di sicurezza intrinseci. È responsabilità dello sviluppatore assicurarsi che il documento JSON Patch sia sicuro da applicare all'oggetto di destinazione. Per altre informazioni, vedere la sezione Mitigazione dei rischi per la sicurezza .
Installazione del pacchetto
Il supporto delle patch JSON nell'API Web di ASP.NET Core si basa su Newtonsoft.Json e richiede il Microsoft.AspNetCore.Mvc.NewtonsoftJson pacchetto NuGet.
Per abilitare il supporto di patch JSON:
Installare il pacchetto NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson.Chiamare AddNewtonsoftJson. Per esempio:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers() .AddNewtonsoftJson(); var app = builder.Build(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
AddNewtonsoftJson sostituisce i formattatori di input e output predefiniti System.Text.Jsonusati per la formattazione di tutto il contenuto JSON. Questo metodo di estensione è compatibile con i metodi di registrazione del servizio MVC seguenti:
JsonPatch richiede l'impostazione dell'intestazione Content-Type su application/json-patch+json.
Aggiunta del supporto per patch JSON quando si usa System.Text.Json
Il System.Text.Jsonformattatore di input basato su non supporta la patch JSON. Per aggiungere il supporto per patch JSON usando Newtonsoft.Json, lasciando invariati gli altri formattatori di input e output:
Installare il pacchetto NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson.Aggiornamento
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(); } }
Il codice precedente crea un'istanza di NewtonsoftJsonPatchInputFormatter e lo inserisce come prima voce nella MvcOptions.InputFormatters raccolta. Questo ordine di registrazione garantisce che:
-
NewtonsoftJsonPatchInputFormatterelabora le richieste patch JSON. - L'input e i formattatori esistenti
System.Text.Jsonelaborano tutte le altre richieste e risposte JSON.
Utilizzare il Newtonsoft.Json.JsonConvert.SerializeObject metodo per serializzare un oggetto JsonPatchDocument.
Metodo di richiesta HTTP PATCH
I metodi PUT e PATCH vengono usati per aggiornare una risorsa esistente. La differenza tra i due metodi è che PUT sostituisce l'intera risorsa, mentre PATCH specifica solo le modifiche.
JSON Patch
JSON Patch è un formato per specificare gli aggiornamenti da applicare a una risorsa. Un documento JSON Patch è una matrice di operazioni. Ogni operazione identifica un particolare tipo di modifica. Esempi di tali modifiche includono l'aggiunta di un elemento matrice o la sostituzione di un valore della proprietà.
Ad esempio, i documenti JSON seguenti rappresentano una risorsa, un documento json patch per la risorsa e il risultato dell'applicazione delle operazioni patch.
Esempio di risorsa
{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}
Esempio di patch JSON
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
Nel codice JSON precedente:
- La proprietà
opindica il tipo di operazione. - La proprietà
pathindica l'elemento da aggiornare. - La proprietà
valuefornisce il nuovo valore.
Risorsa dopo la patch
Ecco la risorsa dopo aver applicato il documento JSON Patch precedente:
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
Le modifiche apportate applicando un documento patch JSON a una risorsa sono atomiche. Se un'operazione nell'elenco ha esito negativo, non viene applicata alcuna operazione nell'elenco.
Sintassi del percorso
La proprietà path di un oggetto operazione include barre tra livelli. Ad esempio: "/address/zipCode".
Vengono usati indici in base zero per specificare gli elementi della matrice. Il primo elemento della matrice addresses è in corrispondenza di /addresses/0. Per add terminare una matrice, usare un trattino (-) anziché un numero di indice: /addresses/-.
Operations
La tabella seguente mostra le operazioni supportate in base a quanto definito nella specifica JSON Patch:
| Operation | Notes |
|---|---|
add |
Aggiunge una proprietà o un elemento della matrice. Per la proprietà esistente: impostare il valore. |
remove |
Rimuove una proprietà o un elemento della matrice. |
replace |
Uguale a remove seguita da add nella stessa posizione. |
move |
Uguale a remove dall'origine seguita da add alla destinazione usando il valore dell'origine. |
copy |
Uguale a add alla destinazione usando il valore dell'origine. |
test |
Restituisce il codice di stato di richiesta riuscita se il valore in path = value fornito. |
JSON Patch in ASP.NET Core
L'implementazione ASP.NET Core di JSON Patch viene fornita nel pacchetto NuGet Microsoft.AspNetCore.JsonPatch.
Codice del metodo di azione
In un controller API un metodo di azione per JSON Patch:
- Viene annotato con l'attributo
HttpPatch. - Accetta un oggetto JsonPatchDocument<TModel>, in genere con
[FromBody]. - Chiama ApplyTo(Object) nel documento di patch per applicare le modifiche.
Ecco un esempio:
[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);
}
}
Questo codice dell'app di esempio funziona con il modello seguente 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; }
}
Il metodo di azione di esempio:
- Costruisce un oggetto
Customer. - Applica la patch.
- Restituisce il risultato nel corpo della risposta.
In un'app reale il codice recupererebbe i dati da un archivio, ad esempio un database, e aggiornerebbe il database dopo aver applicato la patch.
Stato del modello
L'esempio di metodo di azione precedente chiama un overload di ApplyTo che accetta lo stato del modello come uno dei propri parametri. Con questa opzione, è possibile ottenere messaggi di errore nelle risposte. L'esempio seguente mostra il corpo di una risposta 400 Richiesta non valida per un'operazione test:
{
"Customer": [
"The current value 'John' at path 'customerName' != test value 'Nancy'."
]
}
Oggetti dinamici
Nell'esempio di metodo di azione seguente viene illustrato come applicare una patch a un oggetto dinamico:
[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
dynamic obj = new ExpandoObject();
patch.ApplyTo(obj);
return Ok(obj);
}
Operazione add
- Se
pathpunta a un elemento della matrice: inserisce un nuovo elemento prima di quello specificato dapath. - Se
pathpunta a una proprietà: imposta il valore della proprietà. - Se
pathpunta a una posizione inesistente:- Se la risorsa cui applicare la patch è un oggetto dinamico: aggiunge una proprietà.
- Se la risorsa cui applicare la patch è un oggetto statico: la richiesta non riesce.
Il documento di patch di esempio seguente imposta il valore di CustomerName e aggiunge un oggetto Order alla fine della matrice Orders.
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
Operazione remove
- Se
pathpunta a un elemento della matrice: rimuove l'elemento. - Se
pathpunta a una proprietà:- Se la risorsa cui applicare la patch è un oggetto dinamico: rimuove la proprietà.
- Se la risorsa per applicare patch è un oggetto statico:
- Se la proprietà ammette valori Null: la imposta su Null.
- Se la proprietà non ammette valori Null: la imposta su
default<T>.
Il documento di patch di esempio seguente imposta CustomerName su null ed elimina Orders[0]:
[
{
"op": "remove",
"path": "/customerName"
},
{
"op": "remove",
"path": "/orders/0"
}
]
Operazione replace
Questa operazione equivale a livello funzionale all'operazione remove seguita da un'operazione add.
Il documento di patch di esempio seguente imposta il valore di CustomerName e sostituisce Orders[0]con un nuovo Order oggetto:
[
{
"op": "replace",
"path": "/customerName",
"value": "Barry"
},
{
"op": "replace",
"path": "/orders/0",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
Operazione move
- Se
pathpunta a un elemento della matrice: copia l'elementofromnella posizione dell'elementopath, quindi esegue un'operazioneremovesull'elementofrom. - Se
pathpunta a una proprietà: copia il valore della proprietàfromnella proprietàpath, quindi esegue un'operazioneremovesulla proprietàfrom. - Se
pathpunta a una proprietà inesistente:- Se la risorsa cui applicare la patch è un oggetto statico: la richiesta non riesce.
- Se la risorsa cui applicare la patch è un oggetto dinamico: copia la proprietà
fromnella posizione indicata dapath, quindi esegue un'operazioneremovesulla proprietàfrom.
Il documento di patch di esempio seguente:
- Copia il valore di
Orders[0].OrderNameinCustomerName. - Imposta
Orders[0].OrderNamesu Null. - Sposta
Orders[1]prima diOrders[0].
[
{
"op": "move",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "move",
"from": "/orders/1",
"path": "/orders/0"
}
]
Operazione copy
Questa operazione equivale a livello funzionale all'operazione move senza il passaggio remove finale.
Il documento di patch di esempio seguente:
- Copia il valore di
Orders[0].OrderNameinCustomerName. - Inserisce una copia di
Orders[1]prima diOrders[0].
[
{
"op": "copy",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "copy",
"from": "/orders/1",
"path": "/orders/0"
}
]
Operazione test
Se il valore nella posizione indicata da path è diverso dal valore fornito in value, la richiesta non riesce. In questo caso, l'intera richiesta PATCH non riesce anche se tutte le altre operazioni nel documento di patch potrebbero riuscire.
L'operazione test viene usata in genere per impedire un aggiornamento in caso di conflitto di concorrenza.
Il documento di patch di esempio seguente non ha alcun effetto se il valore iniziale di CustomerName è "John", perché il test non riesce:
[
{
"op": "test",
"path": "/customerName",
"value": "Nancy"
},
{
"op": "add",
"path": "/customerName",
"value": "Barry"
}
]
Ottenere il codice
Visualizzare o scaricare il codice di esempio. (Come scaricare un esempio).
Per testare l'esempio, eseguire l'app e inviare richieste HTTP con le impostazioni seguenti:
- URL:
http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate - Metodo HTTP:
PATCH - Intestazione:
Content-Type: application/json-patch+json - Corpo: copiare e incollare uno degli esempi di documenti patch JSON dalla cartella del progetto JSON .
Prevenzione dei rischi per la sicurezza
Quando si usa il pacchetto Microsoft.AspNetCore.JsonPatch con l'implementazione basata su Newtonsoft.Json, è fondamentale comprendere e mitigare i potenziali rischi per la sicurezza. Le sezioni seguenti illustrano i rischi di sicurezza identificati associati alla patch JSON e forniscono mitigazioni consigliate per garantire l'utilizzo sicuro del pacchetto.
Important
Questo non è un elenco completo delle minacce. Gli sviluppatori di app devono condurre revisioni del proprio modello di minaccia per determinare un elenco completo specifico dell'app e trovare soluzioni di mitigazione appropriate in base alle esigenze. Ad esempio, le app che espongono raccolte alle operazioni patch devono considerare il potenziale di attacchi di complessità algoritmica se tali operazioni inseriscono o rimuovono elementi all'inizio della raccolta.
Eseguendo modelli di minaccia completi per le proprie app e risolvendo le minacce identificate seguendo le mitigazioni consigliate di seguito, i consumer di questi pacchetti possono integrare le funzionalità patch JSON nelle proprie app riducendo al minimo i rischi per la sicurezza.
Denial of Service (DoS) tramite amplificazione della memoria
-
Scenario: un client dannoso invia un'operazione
copyche duplica più volte grafici di oggetti di grandi dimensioni, causando un consumo eccessivo di memoria. - Impatto: potenziali condizioni di out-Of-Memory (OOM), causando interruzioni del servizio.
-
Mitigation:
- Convalidare i documenti patch JSON in ingresso per le dimensioni e la struttura prima di chiamare
ApplyTo. - La convalida deve essere specifica dell'app, ma una convalida di esempio può essere simile alla seguente:
- Convalidare i documenti patch JSON in ingresso per le dimensioni e la struttura prima di chiamare
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();
}
}
Subversione della logica di business
- Scenario: le operazioni patch possono modificare i campi con invarianti impliciti (ad esempio flag interni, ID o campi calcolati), violando i vincoli aziendali.
- Impatto: problemi di integrità dei dati e comportamento imprevisto dell'app.
-
Mitigation:
- Utilizzare oggetti POCO con proprietà definite in modo esplicito che possono essere modificate in modo sicuro.
- Evitare di esporre proprietà sensibili o critiche per la sicurezza nell'oggetto di destinazione.
- Se non viene utilizzato alcun oggetto POCO, convalidare l'oggetto modificato dopo l'applicazione delle operazioni per garantire che le regole aziendali e gli invarianti non vengano violati.
Autenticazione e autorizzazione
- Scenario: i client non autenticati o non autorizzati inviano richieste di patch JSON dannose.
- Impatto: accesso non autorizzato per modificare i dati sensibili o interrompere il comportamento dell'app.
-
Mitigation:
- Proteggere gli endpoint che accettano richieste patch JSON con meccanismi di autenticazione e autorizzazione appropriati.
- Limitare l'accesso a client o utenti attendibili con autorizzazioni appropriate.
Risorse aggiuntive
Questo articolo descrive come gestire le richieste JSON Patch in un'API Web ASP.NET Core.
Important
Lo standard JSON Patch presenta rischi di sicurezza intrinseci. Poiché questi rischi sono intrinseci allo standard JSON Patch, questa implementazione non tenta di attenuare i rischi di sicurezza intrinseci. È responsabilità dello sviluppatore assicurarsi che il documento JSON Patch sia sicuro da applicare all'oggetto di destinazione. Per altre informazioni, vedere la sezione Mitigazione dei rischi per la sicurezza .
Installazione del pacchetto
Per abilitare il supporto di patch JSON nell'app, completare la procedura seguente:
Installare il pacchetto NuGet
Microsoft.AspNetCore.Mvc.NewtonsoftJson.Aggiornare il metodo del
Startup.ConfigureServicesprogetto per chiamare AddNewtonsoftJson. Per esempio:services .AddControllersWithViews() .AddNewtonsoftJson();
AddNewtonsoftJson è compatibile con i metodi di registrazione del servizio MVC:
Patch JSON, AddNewtonsoftJson e System.Text.Json
AddNewtonsoftJson sostituisce i System.Text.Jsonformattatori di input e output basati su usati per la formattazione di tutto il contenuto JSON. Per aggiungere il supporto per patch JSON usando Newtonsoft.Json, lasciando invariati gli altri formattatori, aggiornare il metodo del Startup.ConfigureServices progetto come indicato di seguito:
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();
}
Il codice precedente richiede il Microsoft.AspNetCore.Mvc.NewtonsoftJson pacchetto e le istruzioni seguenti 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;
Usare il Newtonsoft.Json.JsonConvert.SerializeObject metodo per serializzare un jsonPatchDocument.
Metodo di richiesta HTTP PATCH
I metodi PUT e PATCH vengono usati per aggiornare una risorsa esistente. La differenza tra i due metodi è che PUT sostituisce l'intera risorsa, mentre PATCH specifica solo le modifiche.
JSON Patch
JSON Patch è un formato per specificare gli aggiornamenti da applicare a una risorsa. Un documento JSON Patch è una matrice di operazioni. Ogni operazione identifica un particolare tipo di modifica. Esempi di tali modifiche includono l'aggiunta di un elemento matrice o la sostituzione di un valore della proprietà.
Ad esempio, i documenti JSON seguenti rappresentano una risorsa, un documento json patch per la risorsa e il risultato dell'applicazione delle operazioni patch.
Esempio di risorsa
{
"customerName": "John",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
}
]
}
Esempio di patch JSON
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
Nel codice JSON precedente:
- La proprietà
opindica il tipo di operazione. - La proprietà
pathindica l'elemento da aggiornare. - La proprietà
valuefornisce il nuovo valore.
Risorsa dopo la patch
Ecco la risorsa dopo aver applicato il documento JSON Patch precedente:
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
Le modifiche apportate applicando un documento patch JSON a una risorsa sono atomiche. Se un'operazione nell'elenco ha esito negativo, non viene applicata alcuna operazione nell'elenco.
Sintassi del percorso
La proprietà path di un oggetto operazione include barre tra livelli. Ad esempio: "/address/zipCode".
Vengono usati indici in base zero per specificare gli elementi della matrice. Il primo elemento della matrice addresses è in corrispondenza di /addresses/0. Per add terminare una matrice, usare un trattino (-) anziché un numero di indice: /addresses/-.
Operations
La tabella seguente mostra le operazioni supportate in base a quanto definito nella specifica JSON Patch:
| Operation | Notes |
|---|---|
add |
Aggiunge una proprietà o un elemento della matrice. Per la proprietà esistente: impostare il valore. |
remove |
Rimuove una proprietà o un elemento della matrice. |
replace |
Uguale a remove seguita da add nella stessa posizione. |
move |
Uguale a remove dall'origine seguita da add alla destinazione usando il valore dell'origine. |
copy |
Uguale a add alla destinazione usando il valore dell'origine. |
test |
Restituisce il codice di stato di richiesta riuscita se il valore in path = value fornito. |
JSON Patch in ASP.NET Core
L'implementazione ASP.NET Core di JSON Patch viene fornita nel pacchetto NuGet Microsoft.AspNetCore.JsonPatch.
Codice del metodo di azione
In un controller API un metodo di azione per JSON Patch:
- Viene annotato con l'attributo
HttpPatch. - Accetta un oggetto
JsonPatchDocument<T>, in genere con[FromBody]. - Chiama
ApplyTonel documento di patch per applicare le modifiche.
Ecco un esempio:
[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);
}
}
Questo codice dell'app di esempio funziona con il modello seguente 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; }
}
}
Il metodo di azione di esempio:
- Costruisce un oggetto
Customer. - Applica la patch.
- Restituisce il risultato nel corpo della risposta.
In un'app reale il codice recupererebbe i dati da un archivio, ad esempio un database, e aggiornerebbe il database dopo aver applicato la patch.
Stato del modello
L'esempio di metodo di azione precedente chiama un overload di ApplyTo che accetta lo stato del modello come uno dei propri parametri. Con questa opzione, è possibile ottenere messaggi di errore nelle risposte. L'esempio seguente mostra il corpo di una risposta 400 Richiesta non valida per un'operazione test:
{
"Customer": [
"The current value 'John' at path 'customerName' is not equal to the test value 'Nancy'."
]
}
Oggetti dinamici
Nell'esempio di metodo di azione seguente viene illustrato come applicare una patch a un oggetto dinamico:
[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
dynamic obj = new ExpandoObject();
patch.ApplyTo(obj);
return Ok(obj);
}
Operazione add
- Se
pathpunta a un elemento della matrice: inserisce un nuovo elemento prima di quello specificato dapath. - Se
pathpunta a una proprietà: imposta il valore della proprietà. - Se
pathpunta a una posizione inesistente:- Se la risorsa cui applicare la patch è un oggetto dinamico: aggiunge una proprietà.
- Se la risorsa cui applicare la patch è un oggetto statico: la richiesta non riesce.
Il documento di patch di esempio seguente imposta il valore di CustomerName e aggiunge un oggetto Order alla fine della matrice Orders.
[
{
"op": "add",
"path": "/customerName",
"value": "Barry"
},
{
"op": "add",
"path": "/orders/-",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
Operazione remove
- Se
pathpunta a un elemento della matrice: rimuove l'elemento. - Se
pathpunta a una proprietà:- Se la risorsa cui applicare la patch è un oggetto dinamico: rimuove la proprietà.
- Se la risorsa per applicare patch è un oggetto statico:
- Se la proprietà ammette valori Null: la imposta su Null.
- Se la proprietà non ammette valori Null: la imposta su
default<T>.
Il documento di patch di esempio seguente imposta CustomerName su null ed elimina Orders[0]:
[
{
"op": "remove",
"path": "/customerName"
},
{
"op": "remove",
"path": "/orders/0"
}
]
Operazione replace
Questa operazione equivale a livello funzionale all'operazione remove seguita da un'operazione add.
Il documento di patch di esempio seguente imposta il valore di CustomerName e sostituisce Orders[0]con un nuovo Order oggetto:
[
{
"op": "replace",
"path": "/customerName",
"value": "Barry"
},
{
"op": "replace",
"path": "/orders/0",
"value": {
"orderName": "Order2",
"orderType": null
}
}
]
Operazione move
- Se
pathpunta a un elemento della matrice: copia l'elementofromnella posizione dell'elementopath, quindi esegue un'operazioneremovesull'elementofrom. - Se
pathpunta a una proprietà: copia il valore della proprietàfromnella proprietàpath, quindi esegue un'operazioneremovesulla proprietàfrom. - Se
pathpunta a una proprietà inesistente:- Se la risorsa cui applicare la patch è un oggetto statico: la richiesta non riesce.
- Se la risorsa cui applicare la patch è un oggetto dinamico: copia la proprietà
fromnella posizione indicata dapath, quindi esegue un'operazioneremovesulla proprietàfrom.
Il documento di patch di esempio seguente:
- Copia il valore di
Orders[0].OrderNameinCustomerName. - Imposta
Orders[0].OrderNamesu Null. - Sposta
Orders[1]prima diOrders[0].
[
{
"op": "move",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "move",
"from": "/orders/1",
"path": "/orders/0"
}
]
Operazione copy
Questa operazione equivale a livello funzionale all'operazione move senza il passaggio remove finale.
Il documento di patch di esempio seguente:
- Copia il valore di
Orders[0].OrderNameinCustomerName. - Inserisce una copia di
Orders[1]prima diOrders[0].
[
{
"op": "copy",
"from": "/orders/0/orderName",
"path": "/customerName"
},
{
"op": "copy",
"from": "/orders/1",
"path": "/orders/0"
}
]
Operazione test
Se il valore nella posizione indicata da path è diverso dal valore fornito in value, la richiesta non riesce. In questo caso, l'intera richiesta PATCH non riesce anche se tutte le altre operazioni nel documento di patch potrebbero riuscire.
L'operazione test viene usata in genere per impedire un aggiornamento in caso di conflitto di concorrenza.
Il documento di patch di esempio seguente non ha alcun effetto se il valore iniziale di CustomerName è "John", perché il test non riesce:
[
{
"op": "test",
"path": "/customerName",
"value": "Nancy"
},
{
"op": "add",
"path": "/customerName",
"value": "Barry"
}
]
Ottenere il codice
Visualizzare o scaricare il codice di esempio. (Come scaricare un esempio).
Per testare l'esempio, eseguire l'app e inviare richieste HTTP con le impostazioni seguenti:
- URL:
http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate - Metodo HTTP:
PATCH - Intestazione:
Content-Type: application/json-patch+json - Corpo: copiare e incollare uno degli esempi di documenti patch JSON dalla cartella del progetto JSON .