Bagikan melalui


Dukungan JSON Patch di API web ASP.NET Core

Artikel ini menjelaskan cara menangani permintaan JSON Patch di API web ASP.NET Core.

Dukungan Patch JSON di API web ASP.NET Core didasarkan pada System.Text.Json serialisasi, dan memerlukan Microsoft.AspNetCore.JsonPatch.SystemTextJson paket NuGet.

Apa itu standar JSON Patch?

Standar JSON Patch:

  • Adalah format standar untuk menjelaskan perubahan yang akan diterapkan ke dokumen JSON.

  • Didefinisikan dalam RFC 6902 dan banyak digunakan dalam API RESTful untuk melakukan pembaruan parsial pada sumber daya JSON.

  • Menjelaskan urutan operasi yang mengubah dokumen JSON seperti:

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

Di aplikasi web, JSON Patch umumnya digunakan dalam operasi PATCH untuk melakukan pembaruan parsial sumber daya. Daripada mengirim seluruh sumber daya untuk pembaruan, klien dapat mengirim dokumen Patch JSON yang hanya berisi perubahan. Patching mengurangi ukuran payload dan meningkatkan efisiensi.

Untuk gambaran umum standar JSON Patch, lihat jsonpatch.com.

Dukungan JSON Patch di API web ASP.NET Core

Dukungan JSON Patch di API web ASP.NET Core didasarkan pada System.Text.Json serialisasi, dimulai dengan .NET 10, menerapkan Microsoft.AspNetCore.JsonPatch berdasarkan System.Text.Json serialisasi. Fitur ini:

Nota

Implementasi Microsoft.AspNetCore.JsonPatch berdasarkan serialisasi System.Text.Json bukanlah pengganti langsung untuk implementasi berbasis sistem lama Newtonsoft.Json. Ini tidak mendukung jenis dinamis, misalnya ExpandoObject.

Penting

Standar Patch JSON memiliki risiko keamanan yang melekat. Karena risiko ini melekat pada standar Patch JSON, implementasi ASP.NET Core tidak mencoba mengurangi risiko keamanan yang melekat. Ini adalah tanggung jawab pengembang untuk memastikan bahwa dokumen Patch JSON aman untuk diterapkan ke objek target. Untuk informasi selengkapnya, lihat bagian Mitigasi Risiko Keamanan .

Mengaktifkan dukungan JSON Patch dengan System.Text.Json

Untuk mengaktifkan dukungan Patch JSON dengan System.Text.Json, instal Microsoft.AspNetCore.JsonPatch.SystemTextJson paket NuGet.

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

Paket ini menyediakan JsonPatchDocument<TModel> kelas untuk mewakili dokumen Patch JSON untuk objek jenis T dan logika kustom untuk menserialisasikan dan mendeserialisasi dokumen JSON Patch menggunakan System.Text.Json. Metode kunci kelas JsonPatchDocument<TModel> adalah ApplyTo(Object), yang menerapkan operasi patch ke objek target jenis T.

Kode metode tindakan menerapkan Patch JSON

Dalam pengontrol API, metode tindakan untuk JSON Patch:

Contoh metode Tindakan Pengontrol:

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

Kode dari aplikasi sampel ini berfungsi dengan model-model berikut: Customer dan Order.

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

Langkah-langkah kunci metode tindakan sampel:

  • Mengambil Data Pelanggan:
    • Metode mengambil Customer objek dari database AppDb menggunakan id yang disediakan.
    • Jika tidak ada Customer objek yang ditemukan, objek akan mengembalikan 404 Not Found respons.
  • Terapkan Patch JSON:
    • Metode ApplyTo(Object) menerapkan operasi JSON Patch dari patchDoc ke objek Customer yang diambil.
    • Jika kesalahan terjadi selama aplikasi patch, seperti operasi atau konflik yang tidak valid, kesalahan tersebut ditangkap oleh delegasi penanganan kesalahan. Delegasi ini menambahkan pesan kesalahan ke ModelState menggunakan nama jenis objek yang terpengaruh dan pesan kesalahan.
  • Validasi ModelState:
    • Setelah menerapkan patch, metode memeriksa ModelState untuk kesalahan.
    • Jika ModelState tidak valid, seperti karena kesalahan patch, maka 400 Bad Request merespons dengan memberikan kesalahan validasi.
  • Kembalikan Pelanggan yang Diperbarui
    • Jika patch berhasil diterapkan dan ModelState valid, metode mengembalikan objek yang diperbarui Customer dalam respons.

Contoh respons kesalahan:

Contoh berikut menunjukkan isi 400 Bad Request respons untuk operasi Patch JSON saat jalur yang ditentukan tidak valid:

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

Menerapkan dokumen Patch JSON ke objek

Contoh berikut menunjukkan cara menggunakan ApplyTo(Object) metode untuk menerapkan dokumen Patch JSON ke objek.

Contoh: Menerapkan JsonPatchDocument<TModel> ke objek

Contoh berikut menunjukkan:

  • Operasi add, replace, dan remove .
  • Operasi pada properti berlapis.
  • Menambahkan item baru ke array.
  • Penggunaan Konverter Enum String JSON dalam dokumen patch JSON.
// Original object
var person = new Person {
    FirstName = "John",
    LastName = "Doe",
    Email = "johndoe@gmail.com",
    PhoneNumbers = [new() {Number = "123-456-7890", Type = PhoneNumberType.Mobile}],
    Address = new Address
    {
        Street = "123 Main St",
        City = "Anytown",
        State = "TX"
    }
};

// Raw JSON patch document
string jsonPatch = """
[
    { "op": "replace", "path": "/FirstName", "value": "Jane" },
    { "op": "remove", "path": "/Email"},
    { "op": "add", "path": "/Address/ZipCode", "value": "90210" },
    { "op": "add", "path": "/PhoneNumbers/-", "value": { "Number": "987-654-3210",
                                                                "Type": "Work" } }
]
""";

// Deserialize the JSON patch document
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);

// Apply the JSON patch document
patchDoc!.ApplyTo(person);

// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));

Contoh sebelumnya menghasilkan output berikut dari objek yang diperbarui:

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

Metode ApplyTo(Object) ini umumnya mengikuti konvensi dan opsi System.Text.Json untuk memproses JsonPatchDocument<TModel>, termasuk perilaku yang dikontrol oleh opsi berikut:

Perbedaan utama antara System.Text.Json dan implementasi baru JsonPatchDocument<TModel> :

  • Jenis runtime objek target, bukan jenis yang dideklarasikan, menentukan patch properti ApplyTo(Object) mana.
  • System.Text.Json deserialisasi bergantung pada jenis yang dinyatakan untuk mengidentifikasi properti yang memenuhi syarat.

Contoh: Menerapkan JsonPatchDocument dengan penanganan kesalahan

Ada berbagai kesalahan yang dapat terjadi saat menerapkan dokumen Patch JSON. Misalnya, objek target mungkin tidak memiliki properti yang ditentukan, atau nilai yang ditentukan mungkin tidak kompatibel dengan jenis properti.

JSON Patch mendukung test operasi, yang memeriksa apakah nilai tertentu sama dengan properti target. Jika tidak, itu akan mengembalikan kesalahan.

Contoh berikut menunjukkan cara menangani kesalahan ini dengan anggun.

Penting

Objek yang diteruskan ke metode ApplyTo(Object) dimodifikasi langsung di tempatnya. Pemanggil bertanggung jawab dalam mengabaikan perubahan jika ada operasi yang gagal.

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

Contoh sebelumnya menghasilkan output berikut:

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

Mengurangi risiko keamanan

Saat menggunakan Microsoft.AspNetCore.JsonPatch.SystemTextJson paket, sangat penting untuk memahami dan mengurangi potensi risiko keamanan. Bagian berikut menguraikan risiko keamanan yang diidentifikasi yang terkait dengan Patch JSON dan memberikan mitigasi yang direkomendasikan untuk memastikan penggunaan paket yang aman.

Penting

Ini bukan daftar ancaman yang lengkap. Pengembang aplikasi harus melakukan tinjauan model ancaman mereka sendiri untuk menentukan daftar komprehensif khusus aplikasi dan membuat mitigasi yang sesuai kebutuhan. Misalnya, aplikasi yang mengekspos koleksi ke operasi patch harus mempertimbangkan potensi serangan kompleksitas algoritma jika operasi tersebut menyisipkan atau menghapus elemen di awal koleksi.

Untuk meminimalkan risiko keamanan saat mengintegrasikan fungsionalitas JSON Patch ke dalam aplikasi mereka, pengembang harus:

  • Jalankan model ancaman komprehensif untuk aplikasi mereka sendiri.
  • Mengatasi ancaman yang diidentifikasi.
  • Ikuti mitigasi yang direkomendasikan di bagian berikut.

Penolakan Layanan (DoS) melalui amplifikasi memori

  • Skenario: Klien berbahaya mengirimkan copy operasi yang menduplikasi grafik objek besar beberapa kali, yang mengarah ke konsumsi memori yang berlebihan.
  • Dampak: Potensi kondisi Out-Of-Memory (OOM), menyebabkan gangguan layanan.
  • Mitigasi:
    • Validasi dokumen JSON Patch masuk untuk ukuran dan struktur sebelum memanggil ApplyTo(Object).
    • Validasi harus spesifik untuk aplikasi, tetapi contoh validasi dapat terlihat mirip dengan yang berikut ini:
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();
    }
}

Pengacauan Logika Bisnis

  • Skenario: Operasi patch dapat memanipulasi bidang dengan invarian implisit (misalnya, bendera internal, ID, atau bidang komputasi), melanggar batasan bisnis.
  • Dampak: Masalah integritas data dan perilaku aplikasi yang tidak diinginkan.
  • Mitigasi:
    • Gunakan POCO (Objek CLR Lama Biasa) dengan properti yang ditentukan secara eksplisit yang aman untuk dimodifikasi.
      • Hindari mengekspos properti sensitif atau kritis keamanan di objek target.
      • Jika objek POCO tidak digunakan, validasi objek yang di-patch setelah menerapkan operasi untuk memastikan aturan bisnis dan invarian tidak dilanggar.

Autentikasi dan otorisasi

  • Skenario: Klien yang tidak diautentikasi atau tidak sah mengirim permintaan Patch JSON berbahaya.
  • Dampak: Akses tidak sah untuk memodifikasi data sensitif atau mengganggu perilaku aplikasi.
  • Mitigasi:
    • Lindungi titik akhir yang menerima permintaan JSON Patch dengan mekanisme autentikasi dan otorisasi yang tepat.
    • Batasi akses ke klien atau pengguna tepercaya dengan izin yang sesuai.

Mendapatkan kode

Melihat atau mengunduh kode sampel. (Cara mengunduh).

Untuk menguji sampel, jalankan aplikasi dan kirim permintaan HTTP dengan pengaturan berikut:

  • URL: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • Metode HTTP: PATCH
  • Judul: Content-Type: application/json-patch+json
  • Isi: Salin dan tempel salah satu sampel dokumen patch JSON dari folder proyek JSON .

Sumber Daya Tambahan:

Artikel ini menjelaskan cara menangani permintaan JSON Patch di API web ASP.NET Core.

Penting

Standar Patch JSON memiliki risiko keamanan yang melekat. Implementasi ini tidak mencoba mengurangi risiko keamanan yang melekat ini. Ini adalah tanggung jawab pengembang untuk memastikan bahwa dokumen Patch JSON aman untuk diterapkan ke objek target. Untuk informasi selengkapnya, lihat bagian Mitigasi Risiko Keamanan .

Penginstalan paket

Dukungan JSON Patch di API web ASP.NET Core didasarkan pada Newtonsoft.Json dan memerlukan Microsoft.AspNetCore.Mvc.NewtonsoftJson paket NuGet.

Untuk mengaktifkan dukungan JSON Patch:

  • Microsoft.AspNetCore.Mvc.NewtonsoftJson Instal paket NuGet.

  • Panggil AddNewtonsoftJson. Contohnya:

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

AddNewtonsoftJson menggantikan pemformat input dan output berbasis default System.Text.Jsonyang digunakan untuk memformat semua konten JSON. Metode ekstensi ini kompatibel dengan metode pendaftaran layanan MVC berikut:

JsonPatch memerlukan pengaturan Content-Type header ke application/json-patch+json.

Menambahkan dukungan untuk JSON Patch saat menggunakan System.Text.Json

Formatter System.Text.Jsoninput berbasis tidak mendukung JSON Patch. Untuk menambahkan dukungan untuk JSON Patch menggunakan Newtonsoft.Json, sambil membiarkan pemformat input dan output lainnya tidak berubah:

  • Microsoft.AspNetCore.Mvc.NewtonsoftJson Instal paket NuGet.

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

Kode sebelumnya membuat instans NewtonsoftJsonPatchInputFormatter dan menyisipkannya sebagai entri pertama dalam MvcOptions.InputFormatters koleksi. Urutan pendaftaran ini memastikan bahwa:

  • NewtonsoftJsonPatchInputFormatter memproses permintaan Patch JSON.
  • Input dan formatter berbasis yang ada System.Text.Jsonmemproses semua permintaan dan respons JSON lainnya.

Newtonsoft.Json.JsonConvert.SerializeObject Gunakan metode untuk membuat JsonPatchDocumentserialisasi .

Metode permintaan HTTP PATCH

Metode PUT dan PATCH digunakan untuk memperbarui sumber daya yang ada. Perbedaannya adalah PUT menggantikan seluruh sumber daya, sementara PATCH hanya menentukan perubahan.

JSON Patch

JSON Patch adalah format untuk menentukan pembaruan yang akan diterapkan ke sumber daya. Dokumen JSON Patch memiliki array operasi. Setiap operasi mengidentifikasi jenis perubahan tertentu. Contoh perubahan tersebut termasuk menambahkan elemen array atau mengganti nilai properti.

Misalnya, dokumen JSON berikut mewakili sumber daya, dokumen Patch JSON untuk sumber daya, dan hasil penerapan operasi Patch.

Contoh sumber daya

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

Contoh patch JSON

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

Dalam JSON sebelumnya:

  • Properti op menunjukkan jenis operasi.
  • Properti path menunjukkan elemen yang akan diperbarui.
  • Properti value menyediakan nilai baru.

Sumber daya setelah patch

Berikut adalah sumber daya setelah menerapkan dokumen Patch JSON sebelumnya:

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

Perubahan yang dilakukan dengan menerapkan dokumen JSON Patch ke sumber daya adalah atomik. Jika ada operasi dalam daftar yang gagal, tidak ada operasi dalam daftar yang diterapkan.

Sintaksis jalur

Properti jalur objek operasi memiliki garis miring antar level. Contohnya,"/address/zipCode".

Indeks berbasis nol digunakan untuk menentukan elemen array. Elemen pertama dari addresses array akan berada di /addresses/0. Untuk add ke akhir array, gunakan tanda hubung (-) daripada nomor indeks: /addresses/-.

Operasional

Tabel berikut ini memperlihatkan operasi yang didukung seperti yang didefinisikan dalam spesifikasi Patch JSON:

Operasi Catatan
add Tambahkan elemen properti atau array. Untuk properti yang ada: tetapkan nilai.
remove Menghapus elemen properti atau array.
replace Sama seperti remove diikuti oleh add di lokasi yang sama.
move Sama seperti remove dari sumber diikuti oleh add ke tujuan menggunakan nilai dari sumber.
copy Sama seperti add tujuan menggunakan nilai dari sumber.
test Mengembalikan kode status keberhasilan jika nilai di path = disediakan value.

Patch JSON di ASP.NET Core

Implementasi ASP.NET Core dari JSON Patch disediakan dalam paket NuGet Microsoft.AspNetCore.JsonPatch .

Kode metode tindakan

Dalam pengontrol API, metode tindakan untuk JSON Patch:

Berikut contohnya:

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

Kode dari aplikasi sampel ini berfungsi dengan model berikut Customer :

namespace JsonPatchSample.Models;

public class Customer
{
    public string? CustomerName { get; set; }
    public List<Order>? Orders { get; set; }
}
namespace JsonPatchSample.Models;

public class Order
{
    public string OrderName { get; set; }
    public string OrderType { get; set; }
}

Metode tindakan sampel:

  • Membangun sebuah Customer.
  • Menerapkan patch.
  • Mengembalikan hasil dalam isi respons.

Dalam aplikasi nyata, kode akan mengambil data dari penyimpanan seperti database dan memperbarui database setelah menerapkan patch.

Status model

Contoh metode tindakan sebelumnya memanggil kelebihan beban ApplyTo yang mengambil status model sebagai salah satu parameternya. Dengan opsi ini, Anda bisa mendapatkan pesan kesalahan sebagai respons. Contoh berikut menunjukkan isi respons Permintaan Buruk 400 untuk test operasi:

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

Objek dinamis

Contoh metode tindakan berikut menunjukkan cara menerapkan patch ke objek dinamis:

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

    return Ok(obj);
}

Operasi tambahkan

  • Jika path menunjuk ke elemen array: menyisipkan elemen baru sebelum elemen yang ditentukan oleh path.
  • Jika path menunjuk ke properti: mengatur nilai properti.
  • Jika path menunjuk ke lokasi yang tidak ada:
    • Jika sumber daya yang akan di-patch adalah objek dinamis: menambahkan properti.
    • Jika sumber daya untuk di-patch adalah objek statis: permintaan gagal.

Contoh dokumen patch berikut mengatur nilai CustomerName dan menambahkan Order objek ke akhir Orders array.

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

Operasi hapus

  • Jika path menunjuk ke elemen array: menghapus elemen .
  • Jika path menunjuk ke properti:
    • Jika sumber daya untuk patch adalah objek dinamis: menghapus properti .
    • Jika sumber daya untuk di-patch adalah objek statis:
      • Jika properti nullable: mengaturnya ke null.
      • Jika properti tidak dapat diubah ke null, atur ke default<T>.

Contoh kumpulan CustomerName dokumen patch berikut ke null dan hapus Orders[0]:

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

Operasi ganti

Operasi ini secara fungsional sama remove dengan diikuti oleh add.

Contoh dokumen patch berikut menetapkan nilai CustomerName dan mengganti Orders[0]dengan objek baru Order :

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

Operasi pemindahan

  • Jika path menunjuk ke elemen array: menyalin from elemen ke lokasi path elemen, maka menjalankan remove operasi pada from elemen .
  • Jika path menunjuk ke properti: menyalin nilai from properti ke path properti, maka menjalankan remove operasi pada from properti .
  • Jika path menunjuk ke properti yang tidak ada:
    • Jika sumber daya untuk di-patch adalah objek statis: permintaan gagal.
    • Jika sumber daya untuk patch adalah objek dinamis: menyalin from properti ke lokasi yang path ditunjukkan oleh remove, maka menjalankan operasi pada from properti .

Contoh dokumen patch berikut:

  • Menyalin nilai Orders[0].OrderName ke CustomerName.
  • Orders[0].OrderName Mengatur ke null.
  • Orders[1] Pindah ke sebelum Orders[0].
[
  {
    "op": "move",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "move",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

Operasi salin

Operasi ini secara fungsional sama move dengan operasi tanpa langkah terakhir remove .

Contoh dokumen patch berikut:

  • Menyalin nilai Orders[0].OrderName ke CustomerName.
  • Menyisipkan salinan Orders[1] sebelum Orders[0].
[
  {
    "op": "copy",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "copy",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

Operasi pengujian

Jika nilai di lokasi yang ditunjukkan oleh path berbeda dari nilai yang disediakan dalam value, permintaan gagal. Dalam hal ini, seluruh permintaan PATCH gagal bahkan jika semua operasi lain dalam dokumen patch akan berhasil.

Operasi test ini umumnya digunakan untuk mencegah pembaruan ketika ada konflik konkurensi.

Contoh dokumen patch berikut tidak berpengaruh jika nilai CustomerName awal adalah "John", karena pengujian gagal:

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

Mendapatkan kode

Melihat atau mengunduh kode sampel. (Cara mengunduh).

Untuk menguji sampel, jalankan aplikasi dan kirim permintaan HTTP dengan pengaturan berikut:

  • URL: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • Metode HTTP: PATCH
  • Judul: Content-Type: application/json-patch+json
  • Isi: Salin dan tempel salah satu sampel dokumen patch JSON dari folder proyek JSON .

Mengurangi risiko keamanan

Saat menggunakan paket Microsoft.AspNetCore.JsonPatch dengan implementasi berbasis Newtonsoft.Json, memahami dan mengurangi potensi risiko keamanan adalah sangat penting. Bagian berikut menguraikan risiko keamanan yang diidentifikasi yang terkait dengan Patch JSON dan memberikan mitigasi yang direkomendasikan untuk memastikan penggunaan paket yang aman.

Penting

Ini bukan daftar ancaman yang lengkap. Pengembang aplikasi harus melakukan tinjauan model ancaman mereka sendiri untuk menentukan daftar komprehensif khusus aplikasi dan membuat mitigasi yang sesuai kebutuhan. Misalnya, aplikasi yang mengekspos koleksi ke operasi patch harus mempertimbangkan potensi serangan kompleksitas algoritma jika operasi tersebut menyisipkan atau menghapus elemen di awal koleksi.

Dengan menjalankan model ancaman komprehensif untuk aplikasi mereka sendiri dan mengatasi ancaman yang diidentifikasi saat mengikuti mitigasi yang direkomendasikan di bawah ini, konsumen paket ini dapat mengintegrasikan fungsionalitas JSON Patch ke dalam aplikasi mereka sambil meminimalkan risiko keamanan.

Penolakan Layanan (DoS) melalui amplifikasi memori

  • Skenario: Klien berbahaya mengirimkan copy operasi yang menduplikasi grafik objek besar beberapa kali, yang mengarah ke konsumsi memori yang berlebihan.
  • Dampak: Potensi kondisi Out-Of-Memory (OOM), menyebabkan gangguan layanan.
  • Mitigasi:
    • Validasi dokumen JSON Patch masuk untuk ukuran dan struktur sebelum memanggil ApplyTo.
    • Validasi harus spesifik untuk aplikasi, tetapi contoh validasi dapat terlihat mirip dengan yang berikut ini:
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();
    }
}

Pengacauan Logika Bisnis

  • Skenario: Operasi patch dapat memanipulasi bidang dengan invarian implisit (misalnya, bendera internal, ID, atau bidang komputasi), melanggar batasan bisnis.
  • Dampak: Masalah integritas data dan perilaku aplikasi yang tidak diinginkan.
  • Mitigasi:
    • Gunakan objek POCO dengan properti yang ditentukan secara eksplisit yang aman untuk dimodifikasi.
    • Hindari mengekspos properti sensitif atau kritis keamanan di objek target.
    • Jika tidak ada objek POCO yang digunakan, validasi objek yang di-patch setelah menerapkan operasi untuk memastikan aturan bisnis dan invarian tidak dilanggar.

Autentikasi dan otorisasi

  • Skenario: Klien yang tidak diautentikasi atau tidak sah mengirim permintaan Patch JSON berbahaya.
  • Dampak: Akses tidak sah untuk memodifikasi data sensitif atau mengganggu perilaku aplikasi.
  • Mitigasi:
    • Lindungi titik akhir yang menerima permintaan JSON Patch dengan mekanisme autentikasi dan otorisasi yang tepat.
    • Batasi akses ke klien atau pengguna tepercaya dengan izin yang sesuai.

Sumber Daya Tambahan:

Artikel ini menjelaskan cara menangani permintaan JSON Patch di API web ASP.NET Core.

Penting

Standar Patch JSON memiliki risiko keamanan yang melekat. Karena risiko ini melekat pada standar Patch JSON, implementasi ini tidak mencoba mengurangi risiko keamanan yang melekat. Ini adalah tanggung jawab pengembang untuk memastikan bahwa dokumen Patch JSON aman untuk diterapkan ke objek target. Untuk informasi selengkapnya, lihat bagian Mitigasi Risiko Keamanan .

Penginstalan paket

Untuk mengaktifkan dukungan JSON Patch di aplikasi Anda, selesaikan langkah-langkah berikut:

  1. Microsoft.AspNetCore.Mvc.NewtonsoftJson Instal paket NuGet.

  2. Perbarui metode proyek Startup.ConfigureServices untuk memanggil AddNewtonsoftJson. Contohnya:

    services
        .AddControllersWithViews()
        .AddNewtonsoftJson();
    

AddNewtonsoftJson kompatibel dengan metode pendaftaran layanan MVC:

JSON Patch, AddNewtonsoftJson, dan System.Text.Json

AddNewtonsoftJson menggantikan System.Text.Jsonpemformat input dan output berbasis yang digunakan untuk memformat semua konten JSON. Untuk menambahkan dukungan untuk JSON Patch menggunakan Newtonsoft.Json, sambil membiarkan formatter lain tidak berubah, perbarui metode proyek Startup.ConfigureServices sebagai berikut:

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

Kode sebelumnya memerlukan Microsoft.AspNetCore.Mvc.NewtonsoftJson paket dan pernyataan berikut 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;

Newtonsoft.Json.JsonConvert.SerializeObject Gunakan metode untuk membuat serial JsonPatchDocument.

Metode permintaan HTTP PATCH

Metode PUT dan PATCH digunakan untuk memperbarui sumber daya yang ada. Perbedaannya adalah PUT menggantikan seluruh sumber daya, sementara PATCH hanya menentukan perubahan.

JSON Patch

JSON Patch adalah format untuk menentukan pembaruan yang akan diterapkan ke sumber daya. Dokumen JSON Patch memiliki array operasi. Setiap operasi mengidentifikasi jenis perubahan tertentu. Contoh perubahan tersebut termasuk menambahkan elemen array atau mengganti nilai properti.

Misalnya, dokumen JSON berikut mewakili sumber daya, dokumen Patch JSON untuk sumber daya, dan hasil penerapan operasi Patch.

Contoh sumber daya

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

Contoh patch JSON

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

Dalam JSON sebelumnya:

  • Properti op menunjukkan jenis operasi.
  • Properti path menunjukkan elemen yang akan diperbarui.
  • Properti value menyediakan nilai baru.

Sumber daya setelah patch

Berikut adalah sumber daya setelah menerapkan dokumen Patch JSON sebelumnya:

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

Perubahan yang dilakukan dengan menerapkan dokumen JSON Patch ke sumber daya adalah atomik. Jika ada operasi dalam daftar yang gagal, tidak ada operasi dalam daftar yang diterapkan.

Sintaksis jalur

Properti jalur objek operasi memiliki garis miring antar level. Contohnya,"/address/zipCode".

Indeks berbasis nol digunakan untuk menentukan elemen array. Elemen pertama dari addresses array akan berada di /addresses/0. Untuk add ke akhir array, gunakan tanda hubung (-) daripada nomor indeks: /addresses/-.

Operasional

Tabel berikut ini memperlihatkan operasi yang didukung seperti yang didefinisikan dalam spesifikasi Patch JSON:

Operasi Catatan
add Tambahkan elemen properti atau array. Untuk properti yang ada: tetapkan nilai.
remove Menghapus elemen properti atau array.
replace Sama seperti remove diikuti oleh add di lokasi yang sama.
move Sama seperti remove dari sumber diikuti oleh add ke tujuan menggunakan nilai dari sumber.
copy Sama seperti add tujuan menggunakan nilai dari sumber.
test Mengembalikan kode status keberhasilan jika nilai di path = disediakan value.

Patch JSON di ASP.NET Core

Implementasi ASP.NET Core dari JSON Patch disediakan dalam paket NuGet Microsoft.AspNetCore.JsonPatch .

Kode metode tindakan

Dalam pengontrol API, metode tindakan untuk JSON Patch:

  • Diannotasikan dengan HttpPatch atribut .
  • JsonPatchDocument<T>Menerima , biasanya dengan [FromBody].
  • ApplyTo Panggilan pada dokumen patch untuk menerapkan perubahan.

Berikut contohnya:

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

Kode dari aplikasi sampel ini berfungsi dengan model berikut Customer :

using System.Collections.Generic;

namespace JsonPatchSample.Models
{
    public class Customer
    {
        public string CustomerName { get; set; }
        public List<Order> Orders { get; set; }
    }
}
namespace JsonPatchSample.Models
{
    public class Order
    {
        public string OrderName { get; set; }
        public string OrderType { get; set; }
    }
}

Metode tindakan sampel:

  • Membangun sebuah Customer.
  • Menerapkan patch.
  • Mengembalikan hasil dalam isi respons.

Dalam aplikasi nyata, kode akan mengambil data dari penyimpanan seperti database dan memperbarui database setelah menerapkan patch.

Status model

Contoh metode tindakan sebelumnya memanggil kelebihan beban ApplyTo yang mengambil status model sebagai salah satu parameternya. Dengan opsi ini, Anda bisa mendapatkan pesan kesalahan sebagai respons. Contoh berikut menunjukkan isi respons Permintaan Buruk 400 untuk test operasi:

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

Objek dinamis

Contoh metode tindakan berikut menunjukkan cara menerapkan patch ke objek dinamis:

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

    return Ok(obj);
}

Operasi tambahkan

  • Jika path menunjuk ke elemen array: menyisipkan elemen baru sebelum elemen yang ditentukan oleh path.
  • Jika path menunjuk ke properti: mengatur nilai properti.
  • Jika path menunjuk ke lokasi yang tidak ada:
    • Jika sumber daya yang akan di-patch adalah objek dinamis: menambahkan properti.
    • Jika sumber daya untuk di-patch adalah objek statis: permintaan gagal.

Contoh dokumen patch berikut mengatur nilai CustomerName dan menambahkan Order objek ke akhir Orders array.

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

Operasi hapus

  • Jika path menunjuk ke elemen array: menghapus elemen .
  • Jika path menunjuk ke properti:
    • Jika sumber daya untuk patch adalah objek dinamis: menghapus properti .
    • Jika sumber daya untuk di-patch adalah objek statis:
      • Jika properti nullable: mengaturnya ke null.
      • Jika properti tidak dapat diubah ke null, atur ke default<T>.

Contoh kumpulan CustomerName dokumen patch berikut ke null dan hapus Orders[0]:

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

Operasi ganti

Operasi ini secara fungsional sama remove dengan diikuti oleh add.

Contoh dokumen patch berikut menetapkan nilai CustomerName dan mengganti Orders[0]dengan objek baru Order :

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

Operasi pemindahan

  • Jika path menunjuk ke elemen array: menyalin from elemen ke lokasi path elemen, maka menjalankan remove operasi pada from elemen .
  • Jika path menunjuk ke properti: menyalin nilai from properti ke path properti, maka menjalankan remove operasi pada from properti .
  • Jika path menunjuk ke properti yang tidak ada:
    • Jika sumber daya untuk di-patch adalah objek statis: permintaan gagal.
    • Jika sumber daya untuk patch adalah objek dinamis: menyalin from properti ke lokasi yang path ditunjukkan oleh remove, maka menjalankan operasi pada from properti .

Contoh dokumen patch berikut:

  • Menyalin nilai Orders[0].OrderName ke CustomerName.
  • Orders[0].OrderName Mengatur ke null.
  • Orders[1] Pindah ke sebelum Orders[0].
[
  {
    "op": "move",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "move",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

Operasi salin

Operasi ini secara fungsional sama move dengan operasi tanpa langkah terakhir remove .

Contoh dokumen patch berikut:

  • Menyalin nilai Orders[0].OrderName ke CustomerName.
  • Menyisipkan salinan Orders[1] sebelum Orders[0].
[
  {
    "op": "copy",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "copy",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

Operasi pengujian

Jika nilai di lokasi yang ditunjukkan oleh path berbeda dari nilai yang disediakan dalam value, permintaan gagal. Dalam hal ini, seluruh permintaan PATCH gagal bahkan jika semua operasi lain dalam dokumen patch akan berhasil.

Operasi test ini umumnya digunakan untuk mencegah pembaruan ketika ada konflik konkurensi.

Contoh dokumen patch berikut tidak berpengaruh jika nilai CustomerName awal adalah "John", karena pengujian gagal:

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

Mendapatkan kode

Melihat atau mengunduh kode sampel. (Cara mengunduh).

Untuk menguji sampel, jalankan aplikasi dan kirim permintaan HTTP dengan pengaturan berikut:

  • URL: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • Metode HTTP: PATCH
  • Judul: Content-Type: application/json-patch+json
  • Isi: Salin dan tempel salah satu sampel dokumen patch JSON dari folder proyek JSON .