ASP.NET Core Web API 中的 JSON 修补程序
本文介绍如何处理 ASP.NET Core Web API 中的 JSON 修补程序请求。
包安装
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
取代基于 System.Text.Json
的默认输入和输出格式化程序,该格式化程序用于设置所有 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 修补程序是一种格式,用于指定要应用于资源的更新。 JSON 修补程序文档有一个操作数组。 每个操作都标识一种特定类型的更改。 此类更改的示例包括添加数组元素或替换属性值。
例如,以下 JSON 文档表示资源、资源的 JSON 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
}
}
]
在上述 JSON 中:
op
属性指示操作的类型。path
属性指示要更新的元素。value
属性提供新值。
修补程序之后的资源
下面是应用上述 JSON 修补程序文档后的资源:
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
通过将 JSON Patch 文档应用于资源所做的更改是原子操作。 如果列表中的任何操作失败,则不会应用列表中的任何操作。
路径语法
操作对象的路径属性的级别之间有斜杠。 例如,"/address/zipCode"
。
使用从零开始的索引来指定数组元素。 addresses
数组的第一个元素将位于 /addresses/0
。 若要将 add
置于数组末尾,请使用连字符 (-
),而不是索引号:/addresses/-
。
Operations
下表显示了 JSON 修补程序规范中定义的支持操作:
操作 | 说明 |
---|---|
add |
添加属性或数组元素。 对于现有属性:设置值。 |
remove |
删除属性或数组元素。 |
replace |
与在相同位置后跟 add 的 remove 相同。 |
move |
与从后跟 add 的源到使用源中的值的目标的 remove 相同。 |
copy |
与到使用源中的值的目标的 add 相同。 |
test |
如果 path 处的值 = 提供的 value ,则返回成功状态代码。 |
ASP.NET Core 中的 JSON Patch
Microsoft.AspNetCore.JsonPatch NuGet 包中提供了 JSON 修补程序的 ASP.NET Core 实现。
操作方法代码
在 API 控制器中,JSON 修补程序的操作方法:
- 使用
HttpPatch
属性进行批注。 - 接受 JsonPatchDocument<TModel>,通常带有
[FromBody]
。 - 在修补程序文档上调用 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"
}
]
替换操作
此操作在功能上与后跟 add
的 remove
相同。
以下示例修补程序文档设置 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[1]
的副本插入到Orders[0]
之前。
[
{
"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 修补程序规范
- IETF RFC 6901 JSON 指针
- JSON 路径文档。 包括指向用于创建 JSON 修补程序文档的资源的链接。
- ASP.NET Core JSON 修补程序源代码
本文介绍如何处理 ASP.NET Core Web API 中的 JSON 修补程序请求。
包安装
要在应用中启用 JSON Patch 支持,请完成以下步骤:
安装
Microsoft.AspNetCore.Mvc.NewtonsoftJson
NuGet 包。更新项目的
Startup.ConfigureServices
方法以调用 AddNewtonsoftJson。 例如:services .AddControllersWithViews() .AddNewtonsoftJson();
AddNewtonsoftJson
与 MVC 服务注册方法兼容:
JSON Patch、AddNewtonsoftJson 和 System.Text.Json
AddNewtonsoftJson
替换了用于格式化所有 JSON 内容的基于 System.Text.Json
的输入和输出格式化程序。 要使用 Newtonsoft.Json
添加对 JSON Patch 的支持,同时使其他格式化程序保持不变,请按如下所示更新项目的 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 修补程序是一种格式,用于指定要应用于资源的更新。 JSON 修补程序文档有一个操作数组。 每个操作都标识一种特定类型的更改。 此类更改的示例包括添加数组元素或替换属性值。
例如,以下 JSON 文档表示资源、资源的 JSON 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
}
}
]
在上述 JSON 中:
op
属性指示操作的类型。path
属性指示要更新的元素。value
属性提供新值。
修补程序之后的资源
下面是应用上述 JSON 修补程序文档后的资源:
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
通过将 JSON Patch 文档应用于资源所做的更改是原子操作。 如果列表中的任何操作失败,则不会应用列表中的任何操作。
路径语法
操作对象的路径属性的级别之间有斜杠。 例如,"/address/zipCode"
。
使用从零开始的索引来指定数组元素。 addresses
数组的第一个元素将位于 /addresses/0
。 若要将 add
置于数组末尾,请使用连字符 (-
),而不是索引号:/addresses/-
。
Operations
下表显示了 JSON 修补程序规范中定义的支持操作:
操作 | 说明 |
---|---|
add |
添加属性或数组元素。 对于现有属性:设置值。 |
remove |
删除属性或数组元素。 |
replace |
与在相同位置后跟 add 的 remove 相同。 |
move |
与从后跟 add 的源到使用源中的值的目标的 remove 相同。 |
copy |
与到使用源中的值的目标的 add 相同。 |
test |
如果 path 处的值 = 提供的 value ,则返回成功状态代码。 |
ASP.NET Core 中的 JSON Patch
Microsoft.AspNetCore.JsonPatch NuGet 包中提供了 JSON 修补程序的 ASP.NET Core 实现。
操作方法代码
在 API 控制器中,JSON 修补程序的操作方法:
- 使用
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"
}
]
替换操作
此操作在功能上与后跟 add
的 remove
相同。
以下示例修补程序文档设置 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[1]
的副本插入到Orders[0]
之前。
[
{
"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 修补程序规范
- IETF RFC 6901 JSON 修补程序路径格式规范
- JSON 路径文档。 包括指向用于创建 JSON 修补程序文档的资源的链接。
- ASP.NET Core JSON 修补程序源代码