Delen via


Ondersteuning voor JSON Patch in ASP.NET Core-web-API

In dit artikel wordt uitgelegd hoe u JSON Patch-aanvragen kunt verwerken in een ASP.NET Core-web-API.

Ondersteuning voor JSON Patch in ASP.NET Core-web-API is gebaseerd op System.Text.Json serialisatie en vereist het Microsoft.AspNetCore.JsonPatch.SystemTextJson NuGet-pakket.

Wat is de JSON Patch-standaard?

De JSON Patch-standaard:

  • Is een standaardindeling voor het beschrijven van wijzigingen die moeten worden toegepast op een JSON-document.

  • Wordt gedefinieerd in RFC 6902 en wordt veel gebruikt in RESTful-API's om gedeeltelijke updates uit te voeren voor JSON-resources.

  • Beschrijft een reeks bewerkingen die een JSON-document wijzigen, zoals:

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

In web-apps wordt JSON Patch vaak gebruikt in een PATCH-bewerking om gedeeltelijke updates van een resource uit te voeren. In plaats van de hele resource voor een update te verzenden, kunnen clients een JSON Patch-document met alleen de wijzigingen verzenden. Patching vermindert de payload en verbetert de efficiëntie.

Zie jsonpatch.com voor een overzicht van de JSON Patch-standaard.

Ondersteuning voor JSON Patch in ASP.NET Core-web-API

Ondersteuning voor JSON Patch in de ASP.NET Core web-API is gebaseerd op System.Text.Json-serialisatie, te beginnen met .NET 10 en geïmplementeerd op basis van Microsoft.AspNetCore.JsonPatch met behulp van System.Text.Json-serialisatie. Deze functie:

  • Vereist het Microsoft.AspNetCore.JsonPatch.SystemTextJson NuGet-pakket.
  • Komt overeen met moderne .NET-procedures door gebruik te maken van de System.Text.Json bibliotheek, die is geoptimaliseerd voor .NET.
  • Biedt verbeterde prestaties en minder geheugengebruik in vergelijking met de verouderde implementatie op basis van een oudere Newtonsoft.Jsonversie. Zie de Newtonsoft.Json voor meer informatie over de verouderde implementatie op basis van .NET.

Note

De implementatie, gebaseerd op Microsoft.AspNetCore.JsonPatchSystem.Text.Json serialisatie, is geen directe vervanging voor de verouderde Newtonsoft.Json-gebaseerde implementatie. Het biedt bijvoorbeeld ExpandoObjectgeen ondersteuning voor dynamische typen.

Important

De JSON Patch-standaard heeft inherente beveiligingsrisico's. Omdat deze risico's inherent zijn aan de JSON Patch-standaard, probeert de ASP.NET Core-implementatie geen inherente beveiligingsrisico's te beperken. Het is de verantwoordelijkheid van de ontwikkelaar om ervoor te zorgen dat het JSON Patch-document veilig kan worden toegepast op het doelobject. Zie de sectie Beveiligingsrisico's beperken voor meer informatie.

Ondersteuning voor JSON Patch inschakelen met System.Text.Json

Als u ondersteuning voor JSON Patch wilt inschakelen, System.Text.Jsoninstalleert u het Microsoft.AspNetCore.JsonPatch.SystemTextJson NuGet-pakket.

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

Dit pakket biedt een JsonPatchDocument<TModel> klasse die een JSON Patch-document vertegenwoordigt voor objecten van het type T en aangepaste logica voor het serialiseren en deserialiseren van JSON Patch-documenten met behulp van System.Text.Json. De belangrijkste methode van de JsonPatchDocument<TModel> klasse is ApplyTo(Object), waarmee de patchbewerkingen worden toegepast op een doelobject van het type T.

Actiemethodecode voor het toepassen van JSON Patch

In een API-controller, een actiemethode voor JSON Patch:

Voorbeeld van controlleractiemethode:

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

Deze code van de voorbeeld-app werkt met de volgende Customer en Order modellen:

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

De belangrijkste stappen van de voorbeeldactiemethode:

  • De klant ophalen:
    • Met de methode wordt een Customer object opgehaald uit de database AppDb met behulp van de opgegeven id.
    • Als er geen Customer object wordt gevonden, wordt er een 404 Not Found antwoord geretourneerd.
  • JSON-patch toepassen:
    • De ApplyTo(Object) methode past de JSON Patch-bewerkingen van de patchDoc toe op het opgehaalde Customer object.
    • Als er fouten optreden tijdens de patchtoepassing, zoals ongeldige bewerkingen of conflicten, worden deze vastgelegd door een gemachtigde voor foutafhandeling. Deze delegate voegt foutberichten toe aan het ModelState met behulp van de typenaam van het betrokken object en het foutbericht.
  • ModelState valideren:
    • Na het toepassen van de patch controleert de methode de ModelState op fouten.
    • Als de ModelState fout ongeldig is, bijvoorbeeld vanwege patchfouten, wordt er een 400 Bad Request antwoord geretourneerd met de validatiefouten.
  • Retourneer de bijgewerkte klant:
    • Als de patch is toegepast en de ModelState patch geldig is, retourneert de methode het bijgewerkte Customer object in het antwoord.

Voorbeeld van een foutbericht:

In het volgende voorbeeld ziet u de hoofdtekst van een 400 Bad Request antwoord voor een JSON Patch-bewerking wanneer het opgegeven pad ongeldig is:

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

Een JSON Patch-document toepassen op een object

In de volgende voorbeelden ziet u hoe u de ApplyTo(Object) methode gebruikt om een JSON Patch-document toe te passen op een object.

Voorbeeld: Een JsonPatchDocument<TModel> toepassen op een object

In het volgende voorbeeld ziet u:

  • De add, replaceen remove bewerkingen.
  • Bewerkingen op geneste eigenschappen.
  • Een nieuw item toevoegen aan een matrix.
  • Een JSON String Enum Converter gebruiken in een JSON-patchdocument.
// 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));

Het vorige voorbeeld resulteert in de volgende uitvoer van het bijgewerkte object:

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

De ApplyTo(Object) methode volgt over het algemeen de conventies en opties voor het verwerken van System.Text.Json de JsonPatchDocument<TModel>, inclusief het gedrag dat wordt beheerd door de volgende opties:

Belangrijke verschillen tussen System.Text.Json en de nieuwe JsonPatchDocument<TModel> implementatie:

  • Het looptijdtype van het doelobject, niet het gedeclareerde type, bepaalt welke eigenschappen door ApplyTo(Object) worden aangepast.
  • System.Text.Json deserialisatie is afhankelijk van het gedeclareerde type om in aanmerking komende eigenschappen te identificeren.

Voorbeeld: Een JsonPatchDocument toepassen met foutafhandeling

Er zijn verschillende fouten die kunnen optreden bij het toepassen van een JSON Patch-document. Het doelobject heeft bijvoorbeeld mogelijk niet de opgegeven eigenschap of de opgegeven waarde is mogelijk niet compatibel met het eigenschapstype.

JSON Patch ondersteunt de test bewerking, waarmee wordt gecontroleerd of een opgegeven waarde gelijk is aan de doeleigenschap. Als dat niet gebeurt, wordt er een fout geretourneerd.

In het volgende voorbeeld ziet u hoe u deze fouten probleemloos kunt afhandelen.

Important

Het object dat aan de ApplyTo(Object) methode wordt doorgegeven, wordt ter plaatse gewijzigd. De beller is verantwoordelijk voor het negeren van wijzigingen als een bewerking mislukt.

// 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));

Het vorige voorbeeld resulteert in de volgende uitvoer:

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

Beveiligingsrisico's beperken

Wanneer u het Microsoft.AspNetCore.JsonPatch.SystemTextJson pakket gebruikt, is het essentieel om mogelijke beveiligingsrisico's te begrijpen en te beperken. In de volgende secties worden de geïdentificeerde beveiligingsrisico's beschreven die zijn gekoppeld aan JSON Patch en worden aanbevolen oplossingen geboden om veilig gebruik van het pakket te garanderen.

Important

Dit is geen volledige lijst met bedreigingen. App-ontwikkelaars moeten hun eigen beoordelingen voor bedreigingsmodellen uitvoeren om een app-specifieke uitgebreide lijst te bepalen en zo nodig passende oplossingen te bedenken. Apps die verzamelingen beschikbaar maken voor patchbewerkingen, moeten bijvoorbeeld rekening houden met het potentieel voor algoritmen complexe aanvallen als deze bewerkingen elementen invoegen of verwijderen aan het begin van de verzameling.

Ontwikkelaars moeten het volgende doen om beveiligingsrisico's te minimaliseren bij het integreren van de JSON Patch-functionaliteit in hun apps:

  • Voer uitgebreide bedreigingsmodellen uit voor hun eigen apps.
  • Identificeer geïdentificeerde bedreigingen.
  • Volg de aanbevolen maatregelen in de volgende gedeelten.

Denial of Service (DoS) via geheugenversterking

  • Scenario: Een kwaadwillende client verzendt een copy bewerking waarmee grote objectgrafieken meerdere keren worden gedupliceerd, wat leidt tot overmatig geheugenverbruik.
  • Impact: Potentiële out-Of-Memory (OOM)-omstandigheden, waardoor serviceonderbrekingen ontstaan.
  • Mitigation:
    • Valideer binnenkomende JSON Patch-documenten voor grootte en structuur voordat u aanroept ApplyTo(Object).
    • De validatie moet specifiek zijn voor de app, maar een voorbeeldvalidatie kan er ongeveer als volgt uitzien:
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();
    }
}

Subversie van bedrijfslogica

  • Scenario: Patchbewerkingen kunnen velden bewerken met impliciete invarianten (bijvoorbeeld interne vlaggen, id's of berekende velden), die zakelijke beperkingen schenden.
  • Impact: Problemen met gegevensintegriteit en onbedoeld app-gedrag.
  • Mitigation:
    • Gebruik POCOs (gewone oude CLR-objecten) met expliciet gedefinieerde eigenschappen die veilig kunnen worden gewijzigd.
      • Vermijd het blootstellen van gevoelige of beveiligingskritieke eigenschappen in het doelobject.
      • Als een POCO-object niet wordt gebruikt, valideert u het gepatchte object na het toepassen van bewerkingen om ervoor te zorgen dat bedrijfsregels en invarianten niet worden geschonden.

Authenticatie en autorisatie

  • Scenario: Niet-geverifieerde of niet-geautoriseerde clients verzenden schadelijke JSON Patch-aanvragen.
  • Impact: onbevoegde toegang om gevoelige gegevens te wijzigen of het gedrag van apps te verstoren.
  • Mitigation:
    • Beveilig eindpunten die JSON Patch-aanvragen accepteren met de juiste verificatie- en autorisatiemechanismen.
    • Beperk de toegang tot vertrouwde clients of gebruikers met de juiste machtigingen.

De code ophalen

voorbeeldcode weergeven of downloaden. (Hoe te downloaden).

Als u het voorbeeld wilt testen, voert u de app uit en verzendt u HTTP-aanvragen met de volgende instellingen:

  • URL: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • HTTP-methode: PATCH
  • Koptekst: Content-Type: application/json-patch+json
  • Hoofdtekst: Kopieer en plak een van de voorbeelden van JSON-patchdocumenten uit de JSON- projectmap.

Aanvullende bronnen

In dit artikel wordt uitgelegd hoe u JSON Patch-aanvragen kunt verwerken in een ASP.NET Core-web-API.

Important

De JSON Patch-standaard heeft inherente beveiligingsrisico's. Deze implementatie probeert deze inherente beveiligingsrisico's niet te beperken. Het is de verantwoordelijkheid van de ontwikkelaar om ervoor te zorgen dat het JSON Patch-document veilig kan worden toegepast op het doelobject. Zie de sectie Beveiligingsrisico's beperken voor meer informatie.

Pakketinstallatie

Ondersteuning voor JSON Patch in ASP.NET Core-web-API is gebaseerd op Newtonsoft.Json en vereist het Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet-pakket.

JSON Patch-ondersteuning inschakelen:

  • Installeer het Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet-pakket.

  • Bel AddNewtonsoftJson. Voorbeeld:

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

AddNewtonsoftJson vervangt de standaard op System.Text.Jsongebaseerde invoer- en uitvoerindelingen die worden gebruikt voor het opmaken van alle JSON-inhoud. Deze extensiemethode is compatibel met de volgende MVC-serviceregistratiemethoden:

JsonPatch vereist het instellen van de Content-Type-header op application/json-patch+json.

Ondersteuning voor JSON Patch toevoegen bij het gebruik van System.Text.Json

De System.Text.Json-based input formatter biedt geen ondersteuning voor JSON Patch. Ondersteuning voor JSON Patch toevoegen met behulp van Newtonsoft.Json, terwijl de andere invoer- en uitvoerindelingen ongewijzigd blijven:

  • Installeer het Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet-pakket.

  • Program.csbijwerken:

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

Met de voorgaande code wordt een exemplaar van NewtonsoftJsonPatchInputFormatter gemaakt en als het eerste element in de MvcOptions.InputFormatters-collectie ingevoegd. Deze registratievolgorde zorgt ervoor dat:

  • NewtonsoftJsonPatchInputFormatter verwerkt JSON Patch-aanvragen.
  • De bestaande op System.Text.Jsongebaseerde input en formatters verwerken alle andere JSON-aanvragen en -antwoorden.

Gebruik de methode Newtonsoft.Json.JsonConvert.SerializeObject om een JsonPatchDocumentte serialiseren.

HTTP-aanvraagmethode PATCH

De methoden PUT en PATCH worden gebruikt om een bestaande resource bij te werken. Het verschil is dat PUT de hele resource vervangt, terwijl PATCH alleen de wijzigingen opgeeft.

JSON-patch

JSON Patch is een indeling voor het opgeven van updates die moeten worden toegepast op een resource. Een JSON Patch-document bevat een matrix met bewerkingen. Elke bewerking identificeert een bepaald type wijziging. Voorbeelden van dergelijke wijzigingen zijn het toevoegen van een matrixelement of het vervangen van een eigenschapswaarde.

De volgende JSON-documenten vertegenwoordigen bijvoorbeeld een resource, een JSON Patch-document voor de resource en het resultaat van het toepassen van de patchbewerkingen.

Resourcevoorbeeld

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

Voorbeeld van JSON-patch

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

In de voorgaande JSON:

  • De eigenschap op geeft het type bewerking aan.
  • De eigenschap path geeft het element aan dat moet worden bijgewerkt.
  • De eigenschap value biedt de nieuwe waarde.

Resource na de patch

Dit is de resource na het toepassen van het voorgaande JSON Patch-document:

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

De wijzigingen die zijn aangebracht door een JSON Patch-document toe te passen op een resource, zijn atomisch. Als een bewerking in de lijst mislukt, wordt er geen bewerking in de lijst toegepast.

Padsyntaxis

De eigenschap van het pad van een bewerkingsobject heeft slashes tussen de niveaus. Bijvoorbeeld "/address/zipCode".

Indexen op basis van nul worden gebruikt om matrixelementen op te geven. Het eerste element van de addresses matrix bevindt zich op /addresses/0. Als u aan het einde van een matrix wilt add, gebruikt u een afbreekstreepje (-) in plaats van een indexnummer: /addresses/-.

Operations

In de volgende tabel ziet u ondersteunde bewerkingen zoals gedefinieerd in de JSON Patch-specificatie:

Operation Notes
add Voeg een eigenschap of matrixelement toe. Voor bestaande eigenschap: waarde instellen.
remove Een eigenschap of matrixelement verwijderen.
replace Hetzelfde als remove gevolgd door add op dezelfde locatie.
move Hetzelfde als remove van de bron, gevolgd door add naar de bestemming met behulp van de waarde uit de bron.
copy Hetzelfde als add naar bestemming met behulp van waarde uit de bron.
test Retourneer de success-statuscode als de waarde op path = opgegeven value.

JSON Patch in ASP.NET Core

De ASP.NET Core-implementatie van JSON Patch is beschikbaar in het Microsoft.AspNetCore.JsonPatch NuGet-pakket.

Code van actiemethode

In een API-controller, een actiemethode voor JSON Patch:

Hier volgt een voorbeeld:

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

Deze code van de voorbeeld-app werkt met het volgende Customer model:

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

De voorbeeldactiemethode:

  • Maakt een Customer.
  • De patch wordt toegepast.
  • Retourneert het resultaat in de hoofdtekst van het antwoord.

In een echte app haalt de code de gegevens op uit een archief, zoals een database, en werkt de database bij nadat de patch is toegepast.

Modelstatus

In het voorgaande voorbeeld van de actiemethode wordt een overbelasting van ApplyTo aangeroepen die de modelstatus als een van de parameters gebruikt. Met deze optie kunt u foutberichten in antwoorden krijgen. In het volgende voorbeeld ziet u de body van een 400 Ongeldige Aanvraagreactie bij een test-bewerking:

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

Dynamische objecten

In het volgende voorbeeld van een actiemethode ziet u hoe u een patch toepast op een dynamisch object:

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

    return Ok(obj);
}

De toevoegbewerking

  • Als path verwijst naar een matrixelement: voegt nieuw element in vóór het element dat is opgegeven door path.
  • Als path verwijst naar een eigenschap: stelt de eigenschapswaarde in.
  • Als path verwijst naar een niet-bestaande locatie:
    • Als de resource die moet worden gepatcht een dynamisch object is: voegt een eigenschap toe.
    • Als de resource om te patchen een statisch object is: de aanvraag mislukt.

In het volgende voorbeeldpatchdocument wordt de waarde van CustomerName ingesteld en wordt een Order-object toegevoegd aan het einde van de Orders-matrix.

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

De verwijderbewerking

  • Als path verwijst naar een matrixelement: verwijdert het element.
  • Als path verwijst naar een eigenschap:
    • Als de resource om te patchen een dynamisch object is, wordt de eigenschap verwijderd.
    • Als de resource om te patchen een statisch object is:
      • Als de eigenschap nullable is: stelt deze in op null.
      • Als de eigenschap niet nullbaar is, stelt u deze in op default<T>.

De volgende voorbeeldpatch stelt document CustomerName in op null en verwijdert Orders[0]:

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

De vervangingsbewerking

Deze bewerking is functioneel hetzelfde als een remove gevolgd door een add.

In het volgende voorbeeldpatchdocument wordt de waarde van CustomerName ingesteld en wordt Orders[0]vervangen door een nieuw Order-object:

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

De verplaatsingsbewerking

  • Als path verwijst naar een matrixelement: kopieert from element naar de locatie van path element en voert vervolgens een remove bewerking uit op het from element.
  • Als path verwijst naar een eigenschap: kopieert de waarde van from eigenschap naar path eigenschap en voert vervolgens een remove bewerking uit op de eigenschap from.
  • Als path verwijst naar een niet-bestaande eigenschap:
    • Als de resource om te patchen een statisch object is: de aanvraag mislukt.
    • Als de resource die moet worden gepatcht een dynamisch object is: kopieert from eigenschap naar de locatie aangegeven door pathen voert vervolgens een remove bewerking uit op de eigenschap from.

Het volgende voorbeeldpatchdocument:

  • Kopieert de waarde van Orders[0].OrderName naar CustomerName.
  • Hiermee stelt u Orders[0].OrderName in op null.
  • Hiermee verplaatst u Orders[1] naar vóór Orders[0].
[
  {
    "op": "move",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "move",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

De kopieerbewerking

Deze bewerking is functioneel hetzelfde als een move bewerking zonder de laatste remove stap.

Het volgende voorbeeldpatchdocument:

  • Kopieert de waarde van Orders[0].OrderName naar CustomerName.
  • Voegt een kopie van Orders[1] in vóór Orders[0].
[
  {
    "op": "copy",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "copy",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

De testbewerking

Als de waarde op de locatie die wordt aangegeven door path verschilt van de waarde in value, mislukt de aanvraag. In dat geval mislukt de hele PATCH-aanvraag, zelfs als alle andere bewerkingen in het patchdocument anders zouden slagen.

De test bewerking wordt vaak gebruikt om een update te voorkomen wanneer er een gelijktijdigheidsconflict is.

Het volgende voorbeeldpatchdocument heeft geen effect als de initiële waarde van CustomerName 'John' is, omdat de test mislukt:

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

De code ophalen

voorbeeldcode weergeven of downloaden. (Hoe te downloaden).

Als u het voorbeeld wilt testen, voert u de app uit en verzendt u HTTP-aanvragen met de volgende instellingen:

  • URL: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • HTTP-methode: PATCH
  • Koptekst: Content-Type: application/json-patch+json
  • Hoofdtekst: Kopieer en plak een van de voorbeelden van JSON-patchdocumenten uit de JSON- projectmap.

Beveiligingsrisico's beperken

Wanneer u het Microsoft.AspNetCore.JsonPatch-pakket gebruikt met de op Newtonsoft.Json gebaseerde implementatie, is het essentieel om potentiële beveiligingsrisico's te begrijpen en te beperken. In de volgende secties worden de geïdentificeerde beveiligingsrisico's beschreven die zijn gekoppeld aan JSON Patch en worden aanbevolen oplossingen geboden om veilig gebruik van het pakket te garanderen.

Important

Dit is geen volledige lijst met bedreigingen. App-ontwikkelaars moeten hun eigen beoordelingen voor bedreigingsmodellen uitvoeren om een app-specifieke uitgebreide lijst te bepalen en zo nodig passende oplossingen te bedenken. Apps die verzamelingen beschikbaar maken voor patchbewerkingen, moeten bijvoorbeeld rekening houden met het potentieel voor algoritmen complexe aanvallen als deze bewerkingen elementen invoegen of verwijderen aan het begin van de verzameling.

Door uitgebreide bedreigingsmodellen voor hun eigen apps uit te voeren en geïdentificeerde bedreigingen aan te pakken terwijl ze de aanbevolen oplossingen hieronder volgen, kunnen consumenten van deze pakketten JSON Patch-functionaliteit integreren in hun apps en tegelijkertijd beveiligingsrisico's minimaliseren.

Denial of Service (DoS) via geheugenversterking

  • Scenario: Een kwaadwillende client verzendt een copy bewerking waarmee grote objectgrafieken meerdere keren worden gedupliceerd, wat leidt tot overmatig geheugenverbruik.
  • Impact: Potentiële out-Of-Memory (OOM)-omstandigheden, waardoor serviceonderbrekingen ontstaan.
  • Mitigation:
    • Valideer binnenkomende JSON Patch-documenten voor grootte en structuur voordat u aanroept ApplyTo.
    • De validatie moet specifiek zijn voor de app, maar een voorbeeldvalidatie kan er ongeveer als volgt uitzien:
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();
    }
}

Subversie van bedrijfslogica

  • Scenario: Patchbewerkingen kunnen velden bewerken met impliciete invarianten (bijvoorbeeld interne vlaggen, id's of berekende velden), die zakelijke beperkingen schenden.
  • Impact: Problemen met gegevensintegriteit en onbedoeld app-gedrag.
  • Mitigation:
    • Gebruik POCO-objecten met expliciet gedefinieerde eigenschappen die veilig kunnen worden gewijzigd.
    • Vermijd het blootstellen van gevoelige of beveiligingskritieke eigenschappen in het doelobject.
    • Als er geen POCO-object wordt gebruikt, valideert u het gepatchte object na het toepassen van bewerkingen om ervoor te zorgen dat bedrijfsregels en invarianten niet worden geschonden.

Authenticatie en autorisatie

  • Scenario: Niet-geverifieerde of niet-geautoriseerde clients verzenden schadelijke JSON Patch-aanvragen.
  • Impact: onbevoegde toegang om gevoelige gegevens te wijzigen of het gedrag van apps te verstoren.
  • Mitigation:
    • Beveilig eindpunten die JSON Patch-aanvragen accepteren met de juiste verificatie- en autorisatiemechanismen.
    • Beperk de toegang tot vertrouwde clients of gebruikers met de juiste machtigingen.

Aanvullende bronnen

In dit artikel wordt uitgelegd hoe u JSON Patch-aanvragen kunt verwerken in een ASP.NET Core-web-API.

Important

De JSON Patch-standaard heeft inherente beveiligingsrisico's. Omdat deze risico's inherent zijn aan de JSON Patch-standaard, probeert deze implementatie geen inherente beveiligingsrisico's te beperken. Het is de verantwoordelijkheid van de ontwikkelaar om ervoor te zorgen dat het JSON Patch-document veilig kan worden toegepast op het doelobject. Zie de sectie Beveiligingsrisico's beperken voor meer informatie.

Pakketinstallatie

Voer de volgende stappen uit om ondersteuning voor JSON Patch in te schakelen in uw app:

  1. Installeer het Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet-pakket.

  2. Werk de Startup.ConfigureServices-methode van het project bij zodat deze AddNewtonsoftJsonaanroept. Voorbeeld:

    services
        .AddControllersWithViews()
        .AddNewtonsoftJson();
    

AddNewtonsoftJson is compatibel met de registratiemethoden van de MVC-service:

JSON Patch, AddNewtonsoftJson en System.Text.Json

AddNewtonsoftJson vervangt de op System.Text.Jsongebaseerde invoer- en uitvoerformatters die worden gebruikt voor het opmaken van alle JSON-content. Als u ondersteuning wilt toevoegen voor JSON Patch met behulp van Newtonsoft.Json, terwijl de andere formatters ongewijzigd blijven, werkt u de Startup.ConfigureServices methode van het project als volgt bij:

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

Voor de voorgaande code zijn het Microsoft.AspNetCore.Mvc.NewtonsoftJson-pakket en de volgende using-instructies vereist:

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;

Gebruik de methode Newtonsoft.Json.JsonConvert.SerializeObject om een JsonPatchDocument te serialiseren.

HTTP-aanvraagmethode PATCH

De methoden PUT en PATCH worden gebruikt om een bestaande resource bij te werken. Het verschil is dat PUT de hele resource vervangt, terwijl PATCH alleen de wijzigingen opgeeft.

JSON-patch

JSON Patch is een indeling voor het opgeven van updates die moeten worden toegepast op een resource. Een JSON Patch-document bevat een matrix met bewerkingen. Elke bewerking identificeert een bepaald type wijziging. Voorbeelden van dergelijke wijzigingen zijn het toevoegen van een matrixelement of het vervangen van een eigenschapswaarde.

De volgende JSON-documenten vertegenwoordigen bijvoorbeeld een resource, een JSON Patch-document voor de resource en het resultaat van het toepassen van de patchbewerkingen.

Resourcevoorbeeld

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

Voorbeeld van JSON-patch

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

In de voorgaande JSON:

  • De eigenschap op geeft het type bewerking aan.
  • De eigenschap path geeft het element aan dat moet worden bijgewerkt.
  • De eigenschap value biedt de nieuwe waarde.

Resource na de patch

Dit is de resource na het toepassen van het voorgaande JSON Patch-document:

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

De wijzigingen die zijn aangebracht door een JSON Patch-document toe te passen op een resource, zijn atomisch. Als een bewerking in de lijst mislukt, wordt er geen bewerking in de lijst toegepast.

Padsyntaxis

De eigenschap van het pad van een bewerkingsobject heeft slashes tussen de niveaus. Bijvoorbeeld "/address/zipCode".

Indexen op basis van nul worden gebruikt om matrixelementen op te geven. Het eerste element van de addresses matrix bevindt zich op /addresses/0. Als u aan het einde van een matrix wilt add, gebruikt u een afbreekstreepje (-) in plaats van een indexnummer: /addresses/-.

Operations

In de volgende tabel ziet u ondersteunde bewerkingen zoals gedefinieerd in de JSON Patch-specificatie:

Operation Notes
add Voeg een eigenschap of matrixelement toe. Voor bestaande eigenschap: waarde instellen.
remove Een eigenschap of matrixelement verwijderen.
replace Hetzelfde als remove gevolgd door add op dezelfde locatie.
move Hetzelfde als remove van de bron, gevolgd door add naar de bestemming met behulp van de waarde uit de bron.
copy Hetzelfde als add naar bestemming met behulp van waarde uit de bron.
test Retourneer de success-statuscode als de waarde op path = opgegeven value.

JSON Patch in ASP.NET Core

De ASP.NET Core-implementatie van JSON Patch is beschikbaar in het Microsoft.AspNetCore.JsonPatch NuGet-pakket.

Code van actiemethode

In een API-controller, een actiemethode voor JSON Patch:

  • Wordt geannoteerd met het kenmerk HttpPatch.
  • Accepteert een JsonPatchDocument<T>, meestal met [FromBody].
  • Roept ApplyTo aan in het patchdocument om de wijzigingen toe te passen.

Hier volgt een voorbeeld:

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

Deze code van de voorbeeld-app werkt met het volgende Customer model:

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

De voorbeeldactiemethode:

  • Maakt een Customer.
  • De patch wordt toegepast.
  • Retourneert het resultaat in de hoofdtekst van het antwoord.

In een echte app haalt de code de gegevens op uit een archief, zoals een database, en werkt de database bij nadat de patch is toegepast.

Modelstatus

In het voorgaande voorbeeld van de actiemethode wordt een overbelasting van ApplyTo aangeroepen die de modelstatus als een van de parameters gebruikt. Met deze optie kunt u foutberichten in antwoorden krijgen. In het volgende voorbeeld ziet u de body van een 400 Ongeldige Aanvraagreactie bij een test-bewerking:

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

Dynamische objecten

In het volgende voorbeeld van een actiemethode ziet u hoe u een patch toepast op een dynamisch object:

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

    return Ok(obj);
}

De toevoegbewerking

  • Als path verwijst naar een matrixelement: voegt nieuw element in vóór het element dat is opgegeven door path.
  • Als path verwijst naar een eigenschap: stelt de eigenschapswaarde in.
  • Als path verwijst naar een niet-bestaande locatie:
    • Als de resource die moet worden gepatcht een dynamisch object is: voegt een eigenschap toe.
    • Als de resource om te patchen een statisch object is: de aanvraag mislukt.

In het volgende voorbeeldpatchdocument wordt de waarde van CustomerName ingesteld en wordt een Order-object toegevoegd aan het einde van de Orders-matrix.

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

De verwijderbewerking

  • Als path verwijst naar een matrixelement: verwijdert het element.
  • Als path verwijst naar een eigenschap:
    • Als de resource om te patchen een dynamisch object is, wordt de eigenschap verwijderd.
    • Als de resource om te patchen een statisch object is:
      • Als de eigenschap nullable is: stelt deze in op null.
      • Als de eigenschap niet nullbaar is, stelt u deze in op default<T>.

De volgende voorbeeldpatch stelt document CustomerName in op null en verwijdert Orders[0]:

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

De vervangingsbewerking

Deze bewerking is functioneel hetzelfde als een remove gevolgd door een add.

In het volgende voorbeeldpatchdocument wordt de waarde van CustomerName ingesteld en wordt Orders[0]vervangen door een nieuw Order-object:

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

De verplaatsingsbewerking

  • Als path verwijst naar een matrixelement: kopieert from element naar de locatie van path element en voert vervolgens een remove bewerking uit op het from element.
  • Als path verwijst naar een eigenschap: kopieert de waarde van from eigenschap naar path eigenschap en voert vervolgens een remove bewerking uit op de eigenschap from.
  • Als path verwijst naar een niet-bestaande eigenschap:
    • Als de resource om te patchen een statisch object is: de aanvraag mislukt.
    • Als de resource die moet worden gepatcht een dynamisch object is: kopieert from eigenschap naar de locatie aangegeven door pathen voert vervolgens een remove bewerking uit op de eigenschap from.

Het volgende voorbeeldpatchdocument:

  • Kopieert de waarde van Orders[0].OrderName naar CustomerName.
  • Hiermee stelt u Orders[0].OrderName in op null.
  • Hiermee verplaatst u Orders[1] naar vóór Orders[0].
[
  {
    "op": "move",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "move",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

De kopieerbewerking

Deze bewerking is functioneel hetzelfde als een move bewerking zonder de laatste remove stap.

Het volgende voorbeeldpatchdocument:

  • Kopieert de waarde van Orders[0].OrderName naar CustomerName.
  • Voegt een kopie van Orders[1] in vóór Orders[0].
[
  {
    "op": "copy",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "copy",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

De testbewerking

Als de waarde op de locatie die wordt aangegeven door path verschilt van de waarde in value, mislukt de aanvraag. In dat geval mislukt de hele PATCH-aanvraag, zelfs als alle andere bewerkingen in het patchdocument anders zouden slagen.

De test bewerking wordt vaak gebruikt om een update te voorkomen wanneer er een gelijktijdigheidsconflict is.

Het volgende voorbeeldpatchdocument heeft geen effect als de initiële waarde van CustomerName 'John' is, omdat de test mislukt:

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

De code ophalen

voorbeeldcode weergeven of downloaden. (Hoe te downloaden).

Als u het voorbeeld wilt testen, voert u de app uit en verzendt u HTTP-aanvragen met de volgende instellingen:

  • URL: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • HTTP-methode: PATCH
  • Koptekst: Content-Type: application/json-patch+json
  • Hoofdtekst: Kopieer en plak een van de voorbeelden van JSON-patchdocumenten uit de JSON- projectmap.