ASP.NET Core Web API 中的 JsonPatch

本文說明如何在 ASP.NET Core Web API 中處理 JS ON Patch 要求。

套件安裝

JSASP.NET Core Web API 中的 ON Patch 支援是以 且需要 Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet 套件為基礎 Newtonsoft.Json 。 若要啟用 JS ON Patch 支援:

  • 安裝 Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet 套件。

  • 呼叫 AddNewtonsoftJson。 例如:

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

AddNewtonsoftJson會取代用於格式化所有JS ON 內容的預設 System.Text.Json 型輸入和輸出格式器。 此擴充方法與下列 MVC 服務註冊方法相容:

使用 System.Text.Json 時新增 JS ON Patch 的支援

System.Text.Json型輸入格式器不支援 JS ON Patch。 若要使用 Newtonsoft.Json 新增 ON Patch 的支援 JS ,同時讓其他輸入和輸出格式器保持不變:

  • 安裝 Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet 套件。

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

上述程式碼會建立 的 NewtonsoftJsonPatchInputFormatter 實例,並將它插入為集合中的 MvcOptions.InputFormatters 第一個專案。 此註冊順序可確保:

  • NewtonsoftJsonPatchInputFormatter 處理 JS ON Patch 要求。
  • 現有的 System.Text.Json 型輸入和格式器會處理所有其他 JS ON 要求和回應。

Newtonsoft.Json.JsonConvert.SerializeObject使用 方法來序列化 JsonPatchDocument

PATCH HTTP 要求方法

PUT 和 PATCH \(英文\) 方法均用來更新現有的資源。 它們之間的差異是 PUT 會取代整個資源,而 PATCH 只會指定變更。

JSON Patch

JS ON Patch是指定要套用至資源之更新的格式。 JSON Patch 檔具有作業陣列。 每個作業都會識別特定類型的變更。 這類變更的範例包括新增陣列專案或取代屬性值。

例如,下列 JS ON 檔代表資源、 JS 資源的 ON Patch 檔,以及套用 Patch 作業的結果。

資源範例

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

JSON 修補程式範例

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

在上述 JS ON 中:

  • op 屬性會指出作業的類型。
  • path 屬性會指出要更新的元素。
  • value 屬性會提供新值。

修補之後的資源

以下是套用上述 JS ON Patch 檔之後的資源:

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

將 ON Patch 檔套用 JS 至資源所做的變更是不可部分完成的。 如果清單中有任何作業失敗,則不會套用清單中的作業。

路徑語法

作業物件的 path \(英文\) 屬性在層級之間有斜線。 例如: "/address/zipCode"

以零為起始的索引可用來指定陣列元素。 addresses 陣列的第一個元素會在 /addresses/0 上。 若要 add 到陣列結尾,請使用連字號 (-) ,而不是索引編號: /addresses/-

Operations

下表顯示ON Patch 規格中所 JS定義的支援作業:

作業 備註
add 加入屬性或陣列元素。 針對現有的屬性:設定值。
remove 移除屬性或陣列元素。
replace remove 之後接著在同一個位置上 add 相同。
move 與從來源 remove 之後接著使用來源的值 add 到目的地相同。
copy 與使用來源的值 add 到目的地相同。
test 如果 path 上的值 = 所提供的 value,即會傳回成功狀態碼。

JSASP.NET Core 中的 ON Patch

MICROSOFT.AspNetCore.JsonPatch NuGet 套件提供 ON Patch 的 ASP.NET Core實 JS 作。

動作方法程式碼

在 API 控制器中,ON Patch 的 JS 動作方法:

以下為範例:

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

來自範例應用程式的此程式碼適用于下列 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; }
}

範例動作方法:

  • 建構 Customer
  • 套用修補檔案。
  • 在回應本文中傳回結果。

在實際的應用程式中,程式碼會從資料庫之類的存放區擷取資料,並在套用修補檔案之後更新資料庫。

模型狀態

上述動作方法範例會呼叫 ApplyTo 的多載,以取得模型狀態作為它的其中一個參數。 使用此選項,您就能在回應中收到錯誤訊息。 下列範例會針對 test 作業顯示「400 不正確的要求」回應的本文:

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

動態物件

下列動作方法範例示範如何將修補程式套用至動態物件:

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

    return Ok(obj);
}

新增作業

  • 如果 path 指向陣列元素:將新元素插入至 path 所指定的元素之前。
  • 如果 path 指向屬性:設定屬性值。
  • 如果 path 指向不存在的位置:
    • 如果要修補的資源是動態物件:加入屬性。
    • 如果要修補的資源是靜態物件:要求失敗。

下列範例修補文件會設定 CustomerName 的值,並將 Order 物件加入至 Orders 陣列的結尾處。

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

移除作業

  • 如果 path 指向陣列元素:移除該元素。
  • 如果 path 指向屬性:
    • 如果要修補的資源是動態物件:移除屬性。
    • 如果要修補的資源是靜態物件:
      • 如果屬性可為 Null:將它設定為 Null。
      • 如果屬性不可為 Null,則將它設定為 default<T>

下列範例修補檔集 CustomerName 為 null 並刪除 Orders[0]

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

取代作業

此作業在功能上與 remove 之後接著 add 相同。

下列修補程式檔範例會設定 的值 CustomerName ,並將 取代 Orders[0] 為新的 Order 物件:

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

移動作業

  • 如果 path 指向陣列元素:將 from 元素複製到 path 元素的位置,然後在 from 元素上執行 remove 作業。
  • 如果 path 指向屬性:將 from 屬性的值複製到 path 屬性,然後在 from 屬性上執行 remove 作業。
  • 如果 path 指向不存在的屬性:
    • 如果要修補的資源是靜態物件:要求失敗。
    • 如果要修補的資源是動態物件:將 from 屬性複製到 path 所指出的位置,然後在 from 屬性上執行 remove 作業。

下列範例修補文件:

  • Orders[0].OrderName 的值複製到 CustomerName
  • Orders[0].OrderName 設定為 Null。
  • Orders[1] 移到 Orders[0] 前面。
[
  {
    "op": "move",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "move",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

複製作業

此作業在功能上與不含最後 remove 步驟的 move 作業相同。

下列範例修補文件:

  • Orders[0].OrderName 的值複製到 CustomerName
  • Orders[0] 前面插入 Orders[1] 的複本。
[
  {
    "op": "copy",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "copy",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

測試作業

如果 path 所指出位置上的值與 value 中所提供的值不同,則要求會失敗。 在該情況下,整個 PATCH 要求會失敗,即使修補文件中的所有其他作業都成功也一樣。

test 作業通常會用來防止在發生並行衝突時進行更新。

如果 CustomerName 的初始值是 "John",則下列範例修補文件不會有任何作用,因為測試失敗:

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

取得程式碼

檢視或下載範例程式碼。 (如何下載)。

若要測試範例,請執行應用程式,並使用下列設定來傳送 HTTP 要求:

  • URL: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • HTTP 方法:PATCH
  • 標題:Content-Type: application/json-patch+json
  • 本文:從 ON 專案資料夾複製並貼上 JSJS其中一個 ON 修補程式檔範例。

其他資源

本文說明如何在 ASP.NET Core Web API 中處理 JS ON Patch 要求。

套件安裝

若要在應用程式中啟用 JS ON Patch 支援,請完成下列步驟:

  1. 安裝 Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet 套件。

  2. 更新專案的 Startup.ConfigureServices 方法來呼叫 AddNewtonsoftJson 。 例如:

    services
        .AddControllersWithViews()
        .AddNewtonsoftJson();
    

AddNewtonsoftJson 與 MVC 服務註冊方法相容:

JSON Patch、AddNewtonsoftJson 和 System.Text.Json

AddNewtonsoftJsonSystem.Text.Json會取代用於格式化所有JS ON 內容的型輸入和輸出格式器。 若要使用 Newtonsoft.Json 新增 ON Patch 的支援 JS ,同時讓其他格式器保持不變,請更新專案的 Startup.ConfigureServices 方法,如下所示:

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

上述程式碼需要 Microsoft.AspNetCore.Mvc.NewtonsoftJson 套件和下列 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使用 方法來序列化 JsonPatchDocument。

PATCH HTTP 要求方法

PUT 和 PATCH \(英文\) 方法均用來更新現有的資源。 它們之間的差異是 PUT 會取代整個資源,而 PATCH 只會指定變更。

JSON Patch

JS ON Patch是指定要套用至資源之更新的格式。 JSON Patch 檔具有作業陣列。 每個作業都會識別特定類型的變更。 這類變更的範例包括新增陣列元素或取代屬性值。

例如,下列 JS ON 檔代表資源、 JS 資源的 ON Patch 檔,以及套用 Patch 作業的結果。

資源範例

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

JSON 修補程式範例

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

在上述 JS ON 中:

  • op 屬性會指出作業的類型。
  • path 屬性會指出要更新的元素。
  • value 屬性會提供新值。

修補之後的資源

以下是套用上述 JS ON Patch 檔之後的資源:

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

將 ON Patch 檔套用 JS 至資源所做的變更是不可部分完成的。 如果清單中有任何作業失敗,則不會套用清單中的作業。

路徑語法

作業物件的 path \(英文\) 屬性在層級之間有斜線。 例如: "/address/zipCode"

以零為起始的索引可用來指定陣列元素。 addresses 陣列的第一個元素會在 /addresses/0 上。 若要 add 到陣列結尾,請使用連字號 (-) ,而不是索引編號: /addresses/-

Operations

下表顯示ON Patch 規格中所 JS定義的支援作業:

作業 備註
add 加入屬性或陣列元素。 針對現有的屬性:設定值。
remove 移除屬性或陣列元素。
replace remove 之後接著在同一個位置上 add 相同。
move 與從來源 remove 之後接著使用來源的值 add 到目的地相同。
copy 與使用來源的值 add 到目的地相同。
test 如果 path 上的值 = 所提供的 value,即會傳回成功狀態碼。

JSASP.NET Core 中的 ON 修補程式

ON Patch 的 ASP.NET Core實作 JS 是在Microsoft.AspNetCore.JsonPatch NuGet 套件中提供。

動作方法程式碼

在 API 控制器中,ON Patch 的 JS 動作方法:

  • 使用 HttpPatch 屬性來標註。
  • JsonPatchDocument<T>接受 ,通常是使用 [FromBody]
  • 呼叫修補文件上的 ApplyTo 以套用變更。

以下為範例:

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

來自範例應用程式的此程式碼可搭配下列 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; }
    }
}

範例動作方法:

  • 建構 Customer
  • 套用修補檔案。
  • 在回應本文中傳回結果。

在實際的應用程式中,程式碼會從資料庫之類的存放區擷取資料,並在套用修補檔案之後更新資料庫。

模型狀態

上述動作方法範例會呼叫 ApplyTo 的多載,以取得模型狀態作為它的其中一個參數。 使用此選項,您就能在回應中收到錯誤訊息。 下列範例會針對 test 作業顯示「400 不正確的要求」回應的本文:

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

動態物件

下列動作方法範例示範如何將修補程式套用至動態物件:

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

    return Ok(obj);
}

新增作業

  • 如果 path 指向陣列元素:將新元素插入至 path 所指定的元素之前。
  • 如果 path 指向屬性:設定屬性值。
  • 如果 path 指向不存在的位置:
    • 如果要修補的資源是動態物件:加入屬性。
    • 如果要修補的資源是靜態物件:要求失敗。

下列範例修補文件會設定 CustomerName 的值,並將 Order 物件加入至 Orders 陣列的結尾處。

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

移除作業

  • 如果 path 指向陣列元素:移除該元素。
  • 如果 path 指向屬性:
    • 如果要修補的資源是動態物件:移除屬性。
    • 如果要修補的資源是靜態物件:
      • 如果屬性可為 Null:將它設定為 Null。
      • 如果屬性不可為 Null,則將它設定為 default<T>

下列範例修補檔集 CustomerName 為 null 並刪除 Orders[0]

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

取代作業

此作業在功能上與 remove 之後接著 add 相同。

下列修補程式檔範例會設定 的值 CustomerName ,並將 取代 Orders[0] 為新的 Order 物件:

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

移動作業

  • 如果 path 指向陣列元素:將 from 元素複製到 path 元素的位置,然後在 from 元素上執行 remove 作業。
  • 如果 path 指向屬性:將 from 屬性的值複製到 path 屬性,然後在 from 屬性上執行 remove 作業。
  • 如果 path 指向不存在的屬性:
    • 如果要修補的資源是靜態物件:要求失敗。
    • 如果要修補的資源是動態物件:將 from 屬性複製到 path 所指出的位置,然後在 from 屬性上執行 remove 作業。

下列範例修補文件:

  • Orders[0].OrderName 的值複製到 CustomerName
  • Orders[0].OrderName 設定為 Null。
  • Orders[1] 移到 Orders[0] 前面。
[
  {
    "op": "move",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "move",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

複製作業

此作業在功能上與不含最後 remove 步驟的 move 作業相同。

下列範例修補文件:

  • Orders[0].OrderName 的值複製到 CustomerName
  • Orders[0] 前面插入 Orders[1] 的複本。
[
  {
    "op": "copy",
    "from": "/orders/0/orderName",
    "path": "/customerName"
  },
  {
    "op": "copy",
    "from": "/orders/1",
    "path": "/orders/0"
  }
]

測試作業

如果 path 所指出位置上的值與 value 中所提供的值不同,則要求會失敗。 在該情況下,整個 PATCH 要求會失敗,即使修補文件中的所有其他作業都成功也一樣。

test 作業通常會用來防止在發生並行衝突時進行更新。

如果 CustomerName 的初始值是 "John",則下列範例修補文件不會有任何作用,因為測試失敗:

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

取得程式碼

檢視或下載範例程式碼。 (如何下載)。

若要測試範例,請執行應用程式,並使用下列設定來傳送 HTTP 要求:

  • URL: http://localhost:{port}/jsonpatch/jsonpatchwithmodelstate
  • HTTP 方法:PATCH
  • 標題:Content-Type: application/json-patch+json
  • 本文:從JS ON專案資料夾複製並貼上其中一個 JS ON 修補程式檔範例。

其他資源