ASP.NET Core Web API 中的 JsonPatch
本文說明如何處理 ASP.NET Core Web API 中的 JSON Patch 要求。
套件安裝
ASP.NET Core Web API 中的 JSON 修補檔支援是以 Newtonsoft.Json
為基礎且需要 Microsoft.AspNetCore.Mvc.NewtonsoftJson
NuGet 套件。 若要啟用 JSON 修補檔支援:
安裝
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
會取代用於格式化 所有 JSON 內容的預設 System.Text.Json
型輸入和輸出格式器。 此擴充方法與下列 MVC 服務註冊方法相容:
JsonPatch 需要將 Content-Type
標頭設定為 application/json-patch+json
。
使用 System.Text.Json 時新增 JSON 修補檔的支援
System.Text.Json
型輸入格式器不支援 JSON修補檔。 若要使用 Newtonsoft.Json
新增對 JSON 修補檔的支援,同時讓其他輸入和輸出格式器保持不變:
安裝
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
處理 JSON 修補檔要求。- 現有
System.Text.Json
型輸入和格式器會處理所有其他 JSON 要求與回應。
使用 Newtonsoft.Json.JsonConvert.SerializeObject
方法序列化 JsonPatchDocument。
PATCH HTTP 要求方法
PUT 和 PATCH \(英文\) 方法均用來更新現有的資源。 它們之間的差異是 PUT 會取代整個資源,而 PATCH 只會指定變更。
JSON 修補程式
JSON Patch \(英文\) 是一種格式,可用來指定要套用至資源的更新。 JSON Patch 文件具有一個「作業」陣列。 每個作業都會識別特定類型的變更。 這類變更的範例包括新增陣列元素或取代屬性值。
例如,下列 JSON 文件代表一個資源、一份適用於該資源的 JSON 修補文件,以及套用修補作業的結果。
資源範例
{
"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
}
}
]
在上述 JSON 中:
op
屬性會指出作業的類型。path
屬性會指出要更新的元素。value
屬性會提供新值。
修補之後的資源
以下是套用上述 JSON Patch 文件之後的資源:
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
將 JSON 修補文件套用至資源所做的變更是不可部分完成的。 如果清單中有任何作業失敗,則不會套用清單中的任何作業。
路徑語法
作業物件的 path \(英文\) 屬性在層級之間有斜線。 例如: "/address/zipCode"
。
以零為起始的索引可用來指定陣列元素。 addresses
陣列的第一個元素會在 /addresses/0
上。 若要將 add
到陣列結尾處,請使用連字號 (-
) 而不是索引號碼:/addresses/-
。
Operations
下表顯示支援的作業,如 JSON Patch 規格 \(英文\) 中所定義:
作業 | 備註 |
---|---|
add |
加入屬性或陣列元素。 針對現有的屬性:設定值。 |
remove |
移除屬性或陣列元素。 |
replace |
與 remove 之後接著在同一個位置上 add 相同。 |
move |
與從來源 remove 之後接著使用來源的值 add 到目的地相同。 |
copy |
與使用來源的值 add 到目的地相同。 |
test |
如果 path 上的值 = 所提供的 value ,即會傳回成功狀態碼。 |
ASP.NET Core 中的 JSON 修補檔
Microsoft.AspNetCore.JsonPatch \(英文\) NuGet 套件中會提供 JSON Patch 的 ASP.NET Core 實作。
動作方法程式碼
在 API 控制器中,JSON Patch 的動作方法:
- 使用
HttpPatch
屬性來標註。 - 通常會使用
[FromBody]
來接受 JsonPatchDocument<TModel>。 - 呼叫修補文件上的 ApplyTo(Object) 以套用變更。
以下為範例:
[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
的值,並使用新的 Order
物件來取代 Orders[0]
:
[
{
"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
- 內文:從 JSON 專案資料夾中複製並貼上其中一個 JSON 修補文件範例。
其他資源
- IETF RFC 5789 PATCH 方法規格 \(英文\)
- IETF RFC 6902 JSON Patch 規格 \(英文\)
- IETF RFC 6901 JSON 指標
- JSON Patch 文件 \(英文\)。 包含用於建立 JSON Patch 文件的資源連結。
- ASP.NET Core JSON Patch 原始程式碼
本文說明如何處理 ASP.NET Core Web API 中的 JSON Patch 要求。
套件安裝
若要在您的應用程式中啟用 JSON 修補檔支援,請完成下列步驟:
安裝
Microsoft.AspNetCore.Mvc.NewtonsoftJson
NuGet 套件。更新專案的
Startup.ConfigureServices
方法以呼叫 AddNewtonsoftJson。 例如:services .AddControllersWithViews() .AddNewtonsoftJson();
AddNewtonsoftJson
與 MVC 服務註冊方法相容:
JSON 修補檔、AddNewtonsoftJson 和 System.Text.Json
AddNewtonsoftJson
會取代用於格式化 所有 JSON 內容的 System.Text.Json
型輸入和輸出格式器。 若要使用 Newtonsoft.Json
新增對 JSON 修補檔的支援,同時讓其他格式器保持不變,請更新專案的 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 修補程式
JSON Patch \(英文\) 是一種格式,可用來指定要套用至資源的更新。 JSON Patch 文件具有一個「作業」陣列。 每個作業都會識別特定類型的變更。 這類變更的範例包括新增陣列元素或取代屬性值。
例如,下列 JSON 文件代表一個資源、一份適用於該資源的 JSON 修補文件,以及套用修補作業的結果。
資源範例
{
"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
}
}
]
在上述 JSON 中:
op
屬性會指出作業的類型。path
屬性會指出要更新的元素。value
屬性會提供新值。
修補之後的資源
以下是套用上述 JSON Patch 文件之後的資源:
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
將 JSON 修補文件套用至資源所做的變更是不可部分完成的。 如果清單中有任何作業失敗,則不會套用清單中的任何作業。
路徑語法
作業物件的 path \(英文\) 屬性在層級之間有斜線。 例如: "/address/zipCode"
。
以零為起始的索引可用來指定陣列元素。 addresses
陣列的第一個元素會在 /addresses/0
上。 若要將 add
到陣列結尾處,請使用連字號 (-
) 而不是索引號碼:/addresses/-
。
Operations
下表顯示支援的作業,如 JSON Patch 規格 \(英文\) 中所定義:
作業 | 備註 |
---|---|
add |
加入屬性或陣列元素。 針對現有的屬性:設定值。 |
remove |
移除屬性或陣列元素。 |
replace |
與 remove 之後接著在同一個位置上 add 相同。 |
move |
與從來源 remove 之後接著使用來源的值 add 到目的地相同。 |
copy |
與使用來源的值 add 到目的地相同。 |
test |
如果 path 上的值 = 所提供的 value ,即會傳回成功狀態碼。 |
ASP.NET Core 中的 JSON 修補檔
Microsoft.AspNetCore.JsonPatch \(英文\) NuGet 套件中會提供 JSON Patch 的 ASP.NET Core 實作。
動作方法程式碼
在 API 控制器中,JSON Patch 的動作方法:
- 使用
HttpPatch
屬性來標註。 - 通常會使用
[FromBody]
來接受JsonPatchDocument<T>
。 - 呼叫修補文件上的
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
的值,並使用新的 Order
物件來取代 Orders[0]
:
[
{
"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
- 內文:從 JSON 專案資料夾中複製並貼上其中一個 JSON 修補文件範例。
其他資源
- IETF RFC 5789 PATCH 方法規格 \(英文\)
- IETF RFC 6902 JSON Patch 規格 \(英文\)
- IETF RFC 6901 JSON Patch 路徑格式規格 \(英文\)
- JSON Patch 文件 \(英文\)。 包含用於建立 JSON Patch 文件的資源連結。
- ASP.NET Core JSON Patch 原始程式碼