Partager via


Prise en charge des correctifs JSON dans l’API web ASP.NET Core

Cet article explique comment gérer les demandes de correctifs JSON dans une API web ASP.NET Core.

La prise en charge de JSON Patch dans l'API Web ASP.NET Core est basée sur la sérialisation System.Text.Json et nécessite le package Microsoft.AspNetCore.JsonPatch.SystemTextJson NuGet.

Quelle est la spécification JSON Patch ?

La norme JSON Patch :

  • Format standard pour décrire les modifications à appliquer à un document JSON.

  • Est défini dans RFC 6902 et est largement utilisé dans les API RESTful pour effectuer des mises à jour partielles des ressources JSON.

  • Décrit une séquence d’opérations qui modifient un document JSON tel que :

    • add
    • remove
    • replace
    • move
    • copy
    • test

Dans les applications web, JSON Patch est couramment utilisé dans une opération PATCH pour effectuer des mises à jour partielles d’une ressource. Au lieu d’envoyer la ressource entière pour une mise à jour, les clients peuvent envoyer un document patch JSON contenant uniquement les modifications. La mise à jour corrective réduit la taille de la charge utile et améliore l’efficacité.

Pour obtenir une vue d’ensemble de la norme de correctif JSON, consultez jsonpatch.com.

Prise en charge des correctifs JSON dans l’API web ASP.NET Core

La prise en charge des correctifs JSON dans l'API web ASP.NET Core repose sur la sérialisation System.Text.Json, à partir de .NET 10, en mettant en œuvre Microsoft.AspNetCore.JsonPatch sur la base de la sérialisation System.Text.Json. Cette fonctionnalité :

  • Nécessite le Microsoft.AspNetCore.JsonPatch.SystemTextJson paquet NuGet.
  • S’aligne sur les pratiques .NET modernes en tirant parti de la System.Text.Json bibliothèque, qui est optimisée pour .NET.
  • Offre des performances améliorées et une utilisation réduite de la mémoire par rapport à l’implémentation héritée Newtonsoft.Json. Pour plus d’informations sur l’implémentation basée sur l'héritage Newtonsoft.Json, consultez la version .NET 9 de cet article.

Remarque

L'implémentation de Microsoft.AspNetCore.JsonPatch, basée sur la sérialisation System.Text.Json, n'est pas un remplacement direct de l'implémentation héritée basée sur Newtonsoft.Json. Il ne prend pas en charge les types dynamiques, par exemple ExpandoObject.

Important

La norme json Patch présente des risques de sécurité inhérents. Étant donné que ces risques sont inhérents à la norme de correctif JSON, l’implémentation ASP.NET Core ne tente pas d’atténuer les risques de sécurité inhérents. Il incombe au développeur de s’assurer que le document patch JSON est sûr de s’appliquer à l’objet cible. Pour plus d’informations, consultez la section Atténuation des risques de sécurité .

Activer la prise en charge de JSON Patch avec System.Text.Json

Pour activer la prise en charge des correctifs JSON avec System.Text.Json, installez le Microsoft.AspNetCore.JsonPatch.SystemTextJson package NuGet.

dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease

Ce package fournit une classe JsonPatchDocument<TModel> pour représenter un document JSON Patch pour les objets de type T et une logique personnalisée pour la sérialisation et la désérialisation des documents JSON Patch à l’aide de System.Text.Json. La méthode clé de la JsonPatchDocument<TModel> classe est ApplyTo(Object), qui applique les opérations correctives à un objet cible de type T.

Code de méthode d'action appliquant JSON Patch

Dans un contrôleur d’API, une méthode d’action pour JSON Patch :

Exemple de méthode d’action du contrôleur :

[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);
}

Ce code de l’exemple d’application fonctionne avec les modèles suivants CustomerOrder :

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();
    }
}

Étapes clés de l’exemple de méthode d’action :

  • Récupérez le client :
    • La méthode récupère un Customer objet de la base de données AppDb à l’aide de l’ID fourni.
    • Si aucun objet n’est Customer trouvé, il retourne une 404 Not Found réponse.
  • Appliquer le correctif JSON :
    • La ApplyTo(Object) méthode applique les opérations de correctif JSON du patchDoc à l’objet récupéré Customer .
    • Si des erreurs se produisent pendant l’application corrective, telles que des opérations ou des conflits non valides, elles sont capturées par un délégué de gestion des erreurs. Ce délégué ajoute des messages d'erreur au ModelState en utilisant le nom de type de l'objet affecté et le message d'erreur.
  • Valider ModelState :
    • Après avoir appliqué le correctif, la méthode vérifie le ModelState pour des erreurs.
    • Si ModelState n’est pas valide, par exemple en raison d’erreurs de correctif, elle retourne une 400 Bad Request réponse avec les erreurs de validation.
  • Retournez le client mis à jour :
    • Si le correctif est appliqué avec succès et si ModelState est valide, la méthode retourne l'objet mis à jour Customer dans la réponse.

Exemple de réponse d’erreur :

L’exemple suivant montre le corps d’une 400 Bad Request réponse pour une opération de correctif JSON lorsque le chemin d’accès spécifié n’est pas valide :

{
  "Customer": [
    "The target location specified by path segment 'foobar' was not found."
  ]
}

Appliquer un document patch JSON à un objet

Les exemples suivants montrent comment utiliser la ApplyTo(Object) méthode pour appliquer un document patch JSON à un objet.

Exemple : Appliquer un JsonPatchDocument<TModel> à un objet

L’exemple suivant illustre cette situation :

  • Les opérations add, replace, et remove.
  • Opérations sur les propriétés imbriquées.
  • Ajout d’un nouvel élément à un tableau.
  • Utilisation d’un convertisseur d’énumération de chaîne JSON dans un document de correctif 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’exemple précédent génère la sortie suivante de l’objet mis à jour :

{
    "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"
        }
    ]
}

La ApplyTo(Object) méthode suit généralement les conventions et les options de System.Text.Json traitement du JsonPatchDocument<TModel>, y compris le comportement contrôlé par les options suivantes :

Principales différences entre System.Text.Json et la nouvelle JsonPatchDocument<TModel> implémentation :

  • Le type d’exécution de l’objet cible, et non le type déclaré, détermine les correctifs de propriétés ApplyTo(Object) .
  • System.Text.Json la désérialisation s’appuie sur le type déclaré pour identifier les propriétés éligibles.

Exemple : Appliquer un JsonPatchDocument avec gestion des erreurs

Il existe différentes erreurs qui peuvent se produire lors de l’application d’un document patch JSON. Par exemple, l’objet cible peut ne pas avoir la propriété spécifiée, ou la valeur spécifiée peut être incompatible avec le type de propriété.

JSON Patch prend en charge l’opération test , qui vérifie si une valeur spécifiée est égale à la propriété cible. Si ce n’est pas le cas, elle retourne une erreur.

L’exemple suivant montre comment gérer ces erreurs correctement.

Important

L’objet passé à la méthode ApplyTo(Object) est modifié en place. L’appelant est responsable de l’abandon des modifications en cas d’échec d’une opération.

// 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’exemple précédent génère la sortie suivante :

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": []
}

Réduction des risques de sécurité

Lorsque vous utilisez le Microsoft.AspNetCore.JsonPatch.SystemTextJson package, il est essentiel de comprendre et d’atténuer les risques de sécurité potentiels. Les sections suivantes décrivent les risques de sécurité identifiés associés au correctif JSON et fournissent des atténuations recommandées pour garantir l’utilisation sécurisée du package.

Important

Il ne s’agit pas d’une liste exhaustive de menaces. Les développeurs d’applications doivent effectuer leurs propres révisions de modèle de menace pour déterminer une liste complète spécifique à l’application et fournir des atténuations appropriées en fonction des besoins. Par exemple, les applications qui exposent des regroupements à des opérations correctives doivent tenir compte du risque d’attaques de complexité algorithmique si ces opérations insèrent ou suppriment des éléments au début de la collection.

Pour réduire les risques de sécurité lors de l’intégration des fonctionnalités de correctif JSON dans leurs applications, les développeurs doivent :

  • Exécutez des modèles de menace complets pour leurs propres applications.
  • Résoudre les menaces identifiées.
  • Suivez les atténuations recommandées dans les sections suivantes.

Déni de service (DoS) via l’amplification de la mémoire

  • Scénario : un client malveillant envoie une copy opération qui duplique plusieurs fois des graphiques d’objets volumineux, ce qui entraîne une consommation excessive de mémoire.
  • Impact : conditions Out-Of-Memory (OOM) potentielles, provoquant des interruptions de service.
  • Atténuation :
    • Validez les documents de patch JSON entrants quant à la taille et à la structure avant d’appeler ApplyTo(Object).
    • La validation doit être spécifique à l’application, mais un exemple de validation peut ressembler à ce qui suit :
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();
    }
}

Subversion de la logique métier

  • Scénario : les opérations correctives peuvent manipuler des champs avec des invariants implicites (par exemple, des indicateurs internes, des ID ou des champs calculés), en violation des contraintes métier.
  • Impact : problèmes d’intégrité des données et comportement inattendu de l’application.
  • Atténuation :
    • Utilisez des objets POC (objets CLR simples) avec des propriétés définies explicitement qui sont sécurisées pour les modifier.
      • Évitez d’exposer des propriétés sensibles ou critiques de sécurité dans l’objet cible.
      • Si un objet POCO n’est pas utilisé, validez l’objet corrigé après avoir appliqué des opérations pour vous assurer que les règles d’entreprise et les invariants ne sont pas violés.

Authentification et autorisation

  • Scénario : les clients non authentifiés ou non autorisés envoient des demandes de correctif JSON malveillantes.
  • Impact : accès non autorisé pour modifier des données sensibles ou perturber le comportement de l’application.
  • Atténuation :
    • Protégez les points de terminaison acceptant les demandes de correctif JSON avec des mécanismes d’authentification et d’autorisation appropriés.
    • Restreindre l’accès aux clients ou utilisateurs approuvés disposant d’autorisations appropriées.

Obtenir le code

Affichez ou téléchargez l’exemple de code. (Guide pratique de téléchargement).

Pour tester l’exemple, exécutez l’application et envoyez des demandes HTTP avec les paramètres suivants :

  • URL : http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • Méthode HTTP : PATCH
  • En-tête : Content-Type: application/json-patch+json
  • Corps : Copiez et collez l’un des exemples de document JSON Patch à partir du dossier de projet JSON.

Ressources supplémentaires

Cet article explique comment gérer les demandes de correctifs JSON dans une API web ASP.NET Core.

Important

La norme json Patch présente des risques de sécurité inhérents. Cette implémentation ne tente pas d’atténuer ces risques de sécurité inhérents. Il incombe au développeur de s’assurer que le document patch JSON est sûr de s’appliquer à l’objet cible. Pour plus d’informations, consultez la section Atténuation des risques de sécurité .

Installation de package

La prise en charge de JSON Patch dans l'API Web ASP.NET Core est basée sur Newtonsoft.Json et nécessite le package Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet.

Pour activer la prise en charge de JSON Patch :

  • Installez le package NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson.

  • Appelez AddNewtonsoftJson. Par exemple :

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddControllers()
        .AddNewtonsoftJson();
    
    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    

AddNewtonsoftJson remplace les formateurs d’entrée et de sortie par défaut basés sur System.Text.Json utilisés pour formater tout le contenu JSON. Cette méthode d'extension est compatible avec les méthodes d'inscription de service MVC suivantes :

JsonPatch nécessite de définir l'en-tête Content-Type sur application/json-patch+json.

Ajout de la prise en charge de JSON Patch lors de l’utilisation de System.Text.Json

Le formateur d’entrée basé sur System.Text.Json ne prend pas en charge JSON Patch. Pour ajouter la prise en charge de JSON Patch à l’aide de Newtonsoft.Json, tout en laissant les autres formateurs d’entrée et de sortie inchangés :

  • Installez le package NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson.

  • Mettez à jour 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();
        }
    }
    

Le code précédent crée une instance de NewtonsoftJsonPatchInputFormatter et l'insère en tant que première entrée de la collection MvcOptions.InputFormatters. Cet ordre d'inscription garantit que :

  • NewtonsoftJsonPatchInputFormatter traite les requêtes de JSON Patch.
  • Les formateurs et les entrées basés sur System.Text.Json actuels gèrent toutes les autres requêtes et réponses JSON.

Utilisez la méthode Newtonsoft.Json.JsonConvert.SerializeObject pour sérialiser un JsonPatchDocument.

Méthode de demande HTTP PATCH

Les méthodes PUT et PATCH sont utilisées pour mettre à jour une ressource existante. La différence est que PUT remplace la ressource complète, tandis que PATCH spécifie uniquement les modifications.

Correctif JSON

JSON Patch est un format qui spécifie des mises à jour à appliquer à une ressource. Un document JSON Patch possède un tableau des opérations. Chaque opération identifie un type particulier de changement. Des exemples de telles modifications incluent l'ajout d'un élément de tableau ou le remplacement d'une valeur de propriété.

Par exemple, les documents JSON suivants représentent une ressource, un document JSON Patch pour la ressource et le résultat de l’application des opérations Patch.

Exemple de ressource

{
  "customerName": "John",
  "orders": [
    {
      "orderName": "Order0",
      "orderType": null
    },
    {
      "orderName": "Order1",
      "orderType": null
    }
  ]
}

Exemple de correctif JSON

[
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "add",
    "path": "/orders/-",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

Dans le code JSON précédent :

  • La propriété op indique le type d’opération.
  • La propriété path indique l’élément à mettre à jour.
  • La propriété value fournit la nouvelle valeur.

Ressources après correction

Voici la ressource après l’application du document JSON Patch précédent :

{
  "customerName": "Barry",
  "orders": [
    {
      "orderName": "Order0",
      "orderType": null
    },
    {
      "orderName": "Order1",
      "orderType": null
    },
    {
      "orderName": "Order2",
      "orderType": null
    }
  ]
}

Les modifications apportées en appliquant un document JSON Patch à une ressource sont atomiques. Si une opération de la liste échoue, aucune opération de la liste n'est appliquée.

Syntaxe du chemin

Les différents niveaux de la propriété path d’un objet de l’opération sont séparés par des barres obliques. Par exemple : "/address/zipCode".

Les index de base zéro sont utilisés pour spécifier les éléments du tableau. Le premier élément du tableau addresses serait à /addresses/0. Pour add à la fin d’un tableau, utilisez un trait d’union (-) plutôt qu’un numéro d’index : /addresses/-.

Opérations

Le tableau suivant mentionne les opérations prises en charge telles qu’elles sont définies dans la spécification JSON Patch :

Opération Remarques
add Ajouter une propriété ou élément de tableau. Pour la propriété existante : définir la valeur.
remove Supprimer une propriété ou un élément de tableau.
replace Identique à remove suivi de add au même emplacement.
move Identique à remove de la source suivi de add à la destination à l’aide de la valeur de la source.
copy Identique à add à la destination à l’aide de la valeur de la source.
test Retourne le code d’état de réussite si la valeur à path = value fournie.

JSON Patch dans ASP.NET Core

L’implémentation ASP.NET Core de JSON Patch est fournie dans le package NuGet Microsoft.AspNetCore.JsonPatch.

Code de méthode d’action

Dans un contrôleur d’API, une méthode d’action pour JSON Patch :

Voici un exemple :

[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);
    }
}

Ce code de l’exemple d’application fonctionne avec le modèle Customer suivant :

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; }
}

L’exemple de méthode d’action :

  • Construit un objet Customer.
  • Applique le correctif.
  • Retourne le résultat dans le corps de la réponse.

Dans une application réelle, le code récupérerait les données dans un magasin tel qu’une base de données et mettrait à jour la base de données après avoir appliqué le correctif.

État du modèle

L’exemple de méthode d’action précédent appelle une surcharge de ApplyTo qui prend l’état du modèle comme un de ses paramètres. Avec cette option, vous pouvez obtenir des messages d’erreur dans les réponses. L’exemple suivant montre le corps d’une demande-réponse incorrecte 400 pour une opération test :

{
  "Customer": [
    "The current value 'John' at path 'customerName' != test value 'Nancy'."
  ]
}

Objets dynamiques

L’exemple de méthode d’action suivant montre comment appliquer un correctif à un objet dynamique :

[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
    dynamic obj = new ExpandoObject();
    patch.ApplyTo(obj);

    return Ok(obj);
}

L’opération d’ajout

  • Si path pointe vers un élément du tableau : insère un nouvel élément avant celui spécifié par path.
  • Si path pointe vers une propriété : définit la valeur de propriété.
  • Si path pointe vers un emplacement qui n’existe pas :
    • Si la ressource à corriger est un objet dynamique : ajoute une propriété.
    • Si la ressource à corriger est un objet statique : la demande échoue.

L’exemple de document de correctif suivant définit la valeur de CustomerName et ajoute un objet Order à la fin du tableau Orders.

[
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "add",
    "path": "/orders/-",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

L’opération de suppression

  • Si path pointe vers un élément du tableau : supprime l’élément.
  • Si path pointe vers une propriété :
    • Si la ressource à corriger est un objet dynamique : supprime la propriété.
    • Si la ressource à corriger est un objet statique :
      • Si la propriété est Nullable : met la valeur sur Null.
      • Si la propriété n’est pas Nullable : met la valeur sur default<T>.

L’exemple suivant de document Patch définit CustomerName sur Null et supprime Orders[0] :

[
  {
    "op": "remove",
    "path": "/customerName"
  },
  {
    "op": "remove",
    "path": "/orders/0"
  }
]

L’opération de remplacement

Cette opération est fonctionnellement identique à remove suivi de add.

L’exemple suivant de document Patch définit la valeur de CustomerName et remplace Orders[0] avec un nouvel objet Order :

[
  {
    "op": "replace",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "replace",
    "path": "/orders/0",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

L’opération de déplacement

  • Si path pointe vers un élément du tableau : copie l’élément from à l’emplacement de l’élément path, puis exécute une opération remove sur l’élément from.
  • Si path pointe vers une propriété : copie la valeur de la propriété from vers la propriété path, puis exécute une opération remove sur la propriété from.
  • Si path pointe vers une propriété qui n’existe pas :
    • Si la ressource à corriger est un objet statique : la demande échoue.
    • Si la ressource à corriger est un objet dynamique : copie la propriété from à un emplacement indiqué par path, puis exécute une opération remove sur la propriété from.

L’exemple de document de correctif suivant :

  • Copie la valeur de Orders[0].OrderName vers CustomerName.
  • Met Orders[0].OrderName sur la valeur Null.
  • Déplace Orders[1] avant Orders[0].
[
  {
    "op": "move",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "move",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

L’opération de copie

Cette opération est fonctionnellement identique à une opération move sans l’étape remove finale.

L’exemple de document de correctif suivant :

  • Copie la valeur de Orders[0].OrderName vers CustomerName.
  • Insère une copie de Orders[1] avant Orders[0].
[
  {
    "op": "copy",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "copy",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

L’opération de test

Si la valeur à l’emplacement indiqué par path est différente de la valeur fournie dans value, la demande échoue. Dans ce cas, toute la demande PATCH échoue, même si toutes les autres opérations dans le document de correctif réussiraient autrement.

L’opération test est généralement utilisée pour empêcher une mise à jour lorsqu’il y a un conflit d’accès concurrentiel.

L’exemple de document de correctif suivant n’a aucun effet si la valeur initiale de CustomerName est « John », car le test échoue :

[
  {
    "op": "test",
    "path": "/customerName",
    "value": "Nancy"
  },
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  }
]

Obtenir le code

Affichez ou téléchargez l’exemple de code. (Guide pratique de téléchargement).

Pour tester l’exemple, exécutez l’application et envoyez des demandes HTTP avec les paramètres suivants :

  • URL : http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • Méthode HTTP : PATCH
  • En-tête : Content-Type: application/json-patch+json
  • Corps : Copiez et collez l’un des exemples de document JSON Patch à partir du dossier de projet JSON.

Réduction des risques de sécurité

Lorsque vous utilisez le Microsoft.AspNetCore.JsonPatch paquet avec l'implémentation basée sur Newtonsoft.Json, il est essentiel de comprendre et d’atténuer les risques de sécurité potentiels. Les sections suivantes décrivent les risques de sécurité identifiés associés au correctif JSON et fournissent des atténuations recommandées pour garantir l’utilisation sécurisée du package.

Important

Il ne s’agit pas d’une liste exhaustive de menaces. Les développeurs d’applications doivent effectuer leurs propres révisions de modèle de menace pour déterminer une liste complète spécifique à l’application et fournir des atténuations appropriées en fonction des besoins. Par exemple, les applications qui exposent des regroupements à des opérations correctives doivent tenir compte du risque d’attaques de complexité algorithmique si ces opérations insèrent ou suppriment des éléments au début de la collection.

En exécutant des modèles de menace complets pour leurs propres applications et en traitant les menaces identifiées tout en suivant les atténuations recommandées ci-dessous, les consommateurs de ces packages peuvent intégrer des fonctionnalités de correctif JSON dans leurs applications tout en réduisant les risques de sécurité.

Déni de service (DoS) via l’amplification de la mémoire

  • Scénario : un client malveillant envoie une copy opération qui duplique plusieurs fois des graphiques d’objets volumineux, ce qui entraîne une consommation excessive de mémoire.
  • Impact : conditions Out-Of-Memory (OOM) potentielles, provoquant des interruptions de service.
  • Atténuation :
    • Validez les documents de patch JSON entrants quant à la taille et à la structure avant d’appeler ApplyTo.
    • La validation doit être spécifique à l’application, mais un exemple de validation peut ressembler à ce qui suit :
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();
    }
}

Subversion de la logique métier

  • Scénario : les opérations correctives peuvent manipuler des champs avec des invariants implicites (par exemple, des indicateurs internes, des ID ou des champs calculés), en violation des contraintes métier.
  • Impact : problèmes d’intégrité des données et comportement inattendu de l’application.
  • Atténuation :
    • Utilisez des objets POCO avec des propriétés définies explicitement qui sont sécurisées à modifier.
    • Évitez d’exposer des propriétés sensibles ou critiques de sécurité dans l’objet cible.
    • Si aucun objet POCO n’est utilisé, validez l’objet corrigé après l’application d’opérations pour vous assurer que les règles d’entreprise et les invariants ne sont pas violés.

Authentification et autorisation

  • Scénario : les clients non authentifiés ou non autorisés envoient des demandes de correctif JSON malveillantes.
  • Impact : accès non autorisé pour modifier des données sensibles ou perturber le comportement de l’application.
  • Atténuation :
    • Protégez les points de terminaison acceptant les demandes de correctif JSON avec des mécanismes d’authentification et d’autorisation appropriés.
    • Restreindre l’accès aux clients ou utilisateurs approuvés disposant d’autorisations appropriées.

Ressources supplémentaires

Cet article explique comment gérer les demandes de correctifs JSON dans une API web ASP.NET Core.

Important

La norme json Patch présente des risques de sécurité inhérents. Étant donné que ces risques sont inhérents à la norme json Patch, cette implémentation ne tente pas d’atténuer les risques de sécurité inhérents. Il incombe au développeur de s’assurer que le document patch JSON est sûr de s’appliquer à l’objet cible. Pour plus d’informations, consultez la section Atténuation des risques de sécurité .

Installation de package

Pour activer la prise en charge de JSON Patch dans votre application, procédez comme suit :

  1. Installez le package NuGet Microsoft.AspNetCore.Mvc.NewtonsoftJson.

  2. Mettez à jour la méthode Startup.ConfigureServices du projet pour appeler AddNewtonsoftJson. Par exemple :

    services
        .AddControllersWithViews()
        .AddNewtonsoftJson();
    

AddNewtonsoftJson est compatible avec les méthodes d’inscription des services MVC :

JSON Patch, AddNewtonsoftJson et System.Text.Json

AddNewtonsoftJson remplace les formateurs d’entrée et de sortie basés sur System.Text.Json utilisés pour formater tout le contenu JSON. Pour ajouter la prise en charge des correctifs JSON en utilisant Newtonsoft.Json, tout en laissant les autres formateurs inchangés, mettez à jour la méthode Startup.ConfigureServices du projet comme suit :

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();
}

Le code précédent requiert le package Microsoft.AspNetCore.Mvc.NewtonsoftJson et les instructions using suivantes :

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;

La méthode Newtonsoft.Json.JsonConvert.SerializeObject permet de sérialiser un JsonPatchDocument.

Méthode de demande HTTP PATCH

Les méthodes PUT et PATCH sont utilisées pour mettre à jour une ressource existante. La différence est que PUT remplace la ressource complète, tandis que PATCH spécifie uniquement les modifications.

Correctif JSON

JSON Patch est un format qui spécifie des mises à jour à appliquer à une ressource. Un document JSON Patch possède un tableau des opérations. Chaque opération identifie un type particulier de changement. Des exemples de telles modifications incluent l'ajout d'un élément de tableau ou le remplacement d'une valeur de propriété.

Par exemple, les documents JSON suivants représentent une ressource, un document JSON Patch pour la ressource et le résultat de l’application des opérations Patch.

Exemple de ressource

{
  "customerName": "John",
  "orders": [
    {
      "orderName": "Order0",
      "orderType": null
    },
    {
      "orderName": "Order1",
      "orderType": null
    }
  ]
}

Exemple de correctif JSON

[
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "add",
    "path": "/orders/-",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

Dans le code JSON précédent :

  • La propriété op indique le type d’opération.
  • La propriété path indique l’élément à mettre à jour.
  • La propriété value fournit la nouvelle valeur.

Ressources après correction

Voici la ressource après l’application du document JSON Patch précédent :

{
  "customerName": "Barry",
  "orders": [
    {
      "orderName": "Order0",
      "orderType": null
    },
    {
      "orderName": "Order1",
      "orderType": null
    },
    {
      "orderName": "Order2",
      "orderType": null
    }
  ]
}

Les modifications apportées en appliquant un document JSON Patch à une ressource sont atomiques. Si une opération de la liste échoue, aucune opération de la liste n'est appliquée.

Syntaxe du chemin

Les différents niveaux de la propriété path d’un objet de l’opération sont séparés par des barres obliques. Par exemple : "/address/zipCode".

Les index de base zéro sont utilisés pour spécifier les éléments du tableau. Le premier élément du tableau addresses serait à /addresses/0. Pour add à la fin d’un tableau, utilisez un trait d’union (-) plutôt qu’un numéro d’index : /addresses/-.

Opérations

Le tableau suivant mentionne les opérations prises en charge telles qu’elles sont définies dans la spécification JSON Patch :

Opération Remarques
add Ajouter une propriété ou élément de tableau. Pour la propriété existante : définir la valeur.
remove Supprimer une propriété ou un élément de tableau.
replace Identique à remove suivi de add au même emplacement.
move Identique à remove de la source suivi de add à la destination à l’aide de la valeur de la source.
copy Identique à add à la destination à l’aide de la valeur de la source.
test Retourne le code d’état de réussite si la valeur à path = value fournie.

JSON Patch dans ASP.NET Core

L’implémentation ASP.NET Core de JSON Patch est fournie dans le package NuGet Microsoft.AspNetCore.JsonPatch.

Code de méthode d’action

Dans un contrôleur d’API, une méthode d’action pour JSON Patch :

  • est annotée avec l’attribut HttpPatch ;
  • Accepte un JsonPatchDocument<T>, généralement avec [FromBody].
  • appelle ApplyTo sur le document de correctif pour appliquer les modifications.

Voici un exemple :

[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);
    }
}

Ce code de l’exemple d’application fonctionne avec le modèle Customer suivant :

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; }
    }
}

L’exemple de méthode d’action :

  • Construit un objet Customer.
  • Applique le correctif.
  • Retourne le résultat dans le corps de la réponse.

Dans une application réelle, le code récupérerait les données dans un magasin tel qu’une base de données et mettrait à jour la base de données après avoir appliqué le correctif.

État du modèle

L’exemple de méthode d’action précédent appelle une surcharge de ApplyTo qui prend l’état du modèle comme un de ses paramètres. Avec cette option, vous pouvez obtenir des messages d’erreur dans les réponses. L’exemple suivant montre le corps d’une demande-réponse incorrecte 400 pour une opération test :

{
    "Customer": [
        "The current value 'John' at path 'customerName' is not equal to the test value 'Nancy'."
    ]
}

Objets dynamiques

L’exemple de méthode d’action suivant montre comment appliquer un correctif à un objet dynamique :

[HttpPatch]
public IActionResult JsonPatchForDynamic([FromBody]JsonPatchDocument patch)
{
    dynamic obj = new ExpandoObject();
    patch.ApplyTo(obj);

    return Ok(obj);
}

L’opération d’ajout

  • Si path pointe vers un élément du tableau : insère un nouvel élément avant celui spécifié par path.
  • Si path pointe vers une propriété : définit la valeur de propriété.
  • Si path pointe vers un emplacement qui n’existe pas :
    • Si la ressource à corriger est un objet dynamique : ajoute une propriété.
    • Si la ressource à corriger est un objet statique : la demande échoue.

L’exemple de document de correctif suivant définit la valeur de CustomerName et ajoute un objet Order à la fin du tableau Orders.

[
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "add",
    "path": "/orders/-",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

L’opération de suppression

  • Si path pointe vers un élément du tableau : supprime l’élément.
  • Si path pointe vers une propriété :
    • Si la ressource à corriger est un objet dynamique : supprime la propriété.
    • Si la ressource à corriger est un objet statique :
      • Si la propriété est Nullable : met la valeur sur Null.
      • Si la propriété n’est pas Nullable : met la valeur sur default<T>.

L’exemple suivant de document Patch définit CustomerName sur Null et supprime Orders[0] :

[
  {
    "op": "remove",
    "path": "/customerName"
  },
  {
    "op": "remove",
    "path": "/orders/0"
  }
]

L’opération de remplacement

Cette opération est fonctionnellement identique à remove suivi de add.

L’exemple suivant de document Patch définit la valeur de CustomerName et remplace Orders[0] avec un nouvel objet Order :

[
  {
    "op": "replace",
    "path": "/customerName",
    "value": "Barry"
  },
  {
    "op": "replace",
    "path": "/orders/0",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }
  }
]

L’opération de déplacement

  • Si path pointe vers un élément du tableau : copie l’élément from à l’emplacement de l’élément path, puis exécute une opération remove sur l’élément from.
  • Si path pointe vers une propriété : copie la valeur de la propriété from vers la propriété path, puis exécute une opération remove sur la propriété from.
  • Si path pointe vers une propriété qui n’existe pas :
    • Si la ressource à corriger est un objet statique : la demande échoue.
    • Si la ressource à corriger est un objet dynamique : copie la propriété from à un emplacement indiqué par path, puis exécute une opération remove sur la propriété from.

L’exemple de document de correctif suivant :

  • Copie la valeur de Orders[0].OrderName vers CustomerName.
  • Met Orders[0].OrderName sur la valeur Null.
  • Déplace Orders[1] avant Orders[0].
[
  {
    "op": "move",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "move",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

L’opération de copie

Cette opération est fonctionnellement identique à une opération move sans l’étape remove finale.

L’exemple de document de correctif suivant :

  • Copie la valeur de Orders[0].OrderName vers CustomerName.
  • Insère une copie de Orders[1] avant Orders[0].
[
  {
    "op": "copy",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "copy",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

L’opération de test

Si la valeur à l’emplacement indiqué par path est différente de la valeur fournie dans value, la demande échoue. Dans ce cas, toute la demande PATCH échoue, même si toutes les autres opérations dans le document de correctif réussiraient autrement.

L’opération test est généralement utilisée pour empêcher une mise à jour lorsqu’il y a un conflit d’accès concurrentiel.

L’exemple de document de correctif suivant n’a aucun effet si la valeur initiale de CustomerName est « John », car le test échoue :

[
  {
    "op": "test",
    "path": "/customerName",
    "value": "Nancy"
  },
  {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"
  }
]

Obtenir le code

Affichez ou téléchargez l’exemple de code. (Guide pratique de téléchargement).

Pour tester l’exemple, exécutez l’application et envoyez des demandes HTTP avec les paramètres suivants :

  • URL : http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • Méthode HTTP : PATCH
  • En-tête : Content-Type: application/json-patch+json
  • Corps : Copiez et collez l’un des exemples de document JSON Patch à partir du dossier de projet JSON.