JsonPatch dans l’API web ASP.NET Core

Cet article explique comment gérer les demandes JSON Patch dans une API Web ASP.NET Core.

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 Microsoft.AspNetCore.Mvc.NewtonsoftJson package NuGet. Pour activer la prise en charge 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 System.Text.Json-d'entrée et de sortie par défaut utilisés pour formater toutJSle contenu ON. Cette méthode d'extension est compatible avec les méthodes d'enregistrement de service MVC suivantes :

JsonPatch nécessite de définir Content-Type en-tête 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 System.Text.Jsonbasé sur – 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 JSON Patch.
  • L'entrée et les formateurs existants System.Text.Jsonbasés sur – traitent toutes les autres demandes et réponses JSON.

Utilisez la méthode Newtonsoft.Json.JsonConvert.SerializeObject pour sérialiser un fichier 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.

JSON Patch

JSON Patch est un format permettant de spécifier les mises à jour à appliquer à une ressource. Un document JSON Patch a un tableau d'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 patch 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 avoir appliqué le 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 pour terminer un tableau, utilisez un trait d'union (-) plutôt qu'un numéro d'index : /addresses/-.

Operations

Le tableau suivant montre les opérations prises en charge telles que définies dans la spécification JSON Patch :

Opération Notes
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 le correctif 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 de document correctif suivant définit CustomerName pour la valeur 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 de document correctif suivant définit la valeur de CustomerName et la remplace Orders[0]par 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 de correctif JSON à partir du dossier du projet JSON.

Ressources supplémentaires

Cet article explique comment gérer les demandes JSON Patch dans une API Web ASP.NET Core.

Installation de package

Pour activer la prise en charge d'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 au service MVC :

JSON Patch, AddNewtonsoftJson et System.Text.Json

AddNewtonsoftJson remplace les formateurs d'entrée et de sortie System.Text.Jsonbasés sur – utilisés pour formater tout le contenuJSON. Pour ajouter la prise en charge de JSON Patch 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 suivantes 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;

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.

JSON Patch

JSON Patch est un format permettant de spécifier les mises à jour à appliquer à une ressource. Un document JSON Patch a un tableau d'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 patch 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 avoir appliqué le 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 pour terminer un tableau, utilisez un trait d'union (-) plutôt qu'un numéro d'index : /addresses/-.

Operations

Le tableau suivant montre les opérations prises en charge telles que définies dans la spécification JSON Patch :

Opération Notes
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 le correctif 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 de document correctif suivant définit CustomerName pour la valeur 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 de document correctif suivant définit la valeur de CustomerName et la remplace Orders[0]par 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 de correctif JSON à partir du dossier du projet JSON.

Ressources supplémentaires