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
では、すべての 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 パッチは、リソースに適用される更新を指定するための形式です。 JSON パッチ ドキュメントには、"操作" の配列が含まれます。 各操作により、特定の種類の変更が識別されます。 このような変更の例としては、配列要素の追加やプロパティ値の置き換えがあります。
たとえば、次の JSON ドキュメントは、1 つのリソースとそのリソースに対応する 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 パッチ ドキュメントを適用した後のリソースを、以下に示します。
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
JSON パッチ ドキュメントをリソースに適用することによって行われた変更はアトミックです。 リスト内のいずれかの操作が失敗した場合、リスト内のどの操作も適用されません。
パス構文
操作オブジェクトの path プロパティでは、レベル間にスラッシュを保持します。 たとえば、「 "/address/zipCode"
」のように入力します。
0 から始まるインデックスは、配列の要素を指定するために使用されます。 addresses
配列の最初の要素は、/addresses/0
にあります。 配列の末尾への add
では、インデックス番号ではなく、/addresses/-
のようにハイフン (-
) を使用します。
操作
次の表は、JSON パッチの仕様に定義されている、サポートされる操作を示しています。
操作 | メモ |
---|---|
add |
プロパティまたは配列要素を追加します。 既存のプロパティの場合: 値を設定します。 |
remove |
プロパティまたは配列要素を削除します。 |
replace |
remove の後に、同じ場所で add が続く場合と同じです。 |
move |
ソースからの remove の後に、ソースからの値を使用した宛先への add が続く場合と同じです。 |
copy |
ソースからの値を使用した宛先への add と同じです。 |
test |
path の値が指定された value と一致する場合に、成功の状態コードを返します。 |
ASP.NET Core 内の JSON パッチ
JSON パッチの ASP.NET Core 実装は、Microsoft.AspNetCore.JsonPatch NuGet パッケージ内に提供されています。
アクション メソッド コード
API コントローラーにおける JSON パッチ用のアクション メソッド:
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
を構築します。- パッチを適用します
- 応答の本文内で結果を返します。
実際のアプリでは、コードはデータベースなどの保存場所からデータを取得し、パッチを適用した後にデータベースを更新します。
モデルの状態
前のアクション メソッドの例では、パラメーターの 1 つとしてモデルの状態を取得する ApplyTo
のオーバーロードを呼び出しています。 このオプションを利用すると、応答内にエラー メッセージを取得できます。 次の例では、test
操作に対する 400 Bad Request 応答の本文を示しています。
{
"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 パッチ ドキュメント サンプルの 1 つをコピーして貼り付けます。
その他のリソース
- 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 パッチのサポートを有効にするには、次の手順を実行します。
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 パッチは、リソースに適用される更新を指定するための形式です。 JSON パッチ ドキュメントには、"操作" の配列が含まれます。 各操作により、特定の種類の変更が識別されます。 このような変更の例としては、配列要素の追加やプロパティ値の置き換えがあります。
たとえば、次の JSON ドキュメントは、1 つのリソースとそのリソースに対応する 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 パッチ ドキュメントを適用した後のリソースを、以下に示します。
{
"customerName": "Barry",
"orders": [
{
"orderName": "Order0",
"orderType": null
},
{
"orderName": "Order1",
"orderType": null
},
{
"orderName": "Order2",
"orderType": null
}
]
}
JSON パッチ ドキュメントをリソースに適用することによって行われた変更はアトミックです。 リスト内のいずれかの操作が失敗した場合、リスト内のどの操作も適用されません。
パス構文
操作オブジェクトの path プロパティでは、レベル間にスラッシュを保持します。 たとえば、「 "/address/zipCode"
」のように入力します。
0 から始まるインデックスは、配列の要素を指定するために使用されます。 addresses
配列の最初の要素は、/addresses/0
にあります。 配列の末尾への add
では、インデックス番号ではなく、/addresses/-
のようにハイフン (-
) を使用します。
操作
次の表は、JSON パッチの仕様に定義されている、サポートされる操作を示しています。
操作 | メモ |
---|---|
add |
プロパティまたは配列要素を追加します。 既存のプロパティの場合: 値を設定します。 |
remove |
プロパティまたは配列要素を削除します。 |
replace |
remove の後に、同じ場所で add が続く場合と同じです。 |
move |
ソースからの remove の後に、ソースからの値を使用した宛先への add が続く場合と同じです。 |
copy |
ソースからの値を使用した宛先への add と同じです。 |
test |
path の値が指定された value と一致する場合に、成功の状態コードを返します。 |
ASP.NET Core 内の JSON パッチ
JSON パッチの ASP.NET Core 実装は、Microsoft.AspNetCore.JsonPatch NuGet パッケージ内に提供されています。
アクション メソッド コード
API コントローラーにおける JSON パッチ用のアクション メソッド:
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
を構築します。- パッチを適用します
- 応答の本文内で結果を返します。
実際のアプリでは、コードはデータベースなどの保存場所からデータを取得し、パッチを適用した後にデータベースを更新します。
モデルの状態
前のアクション メソッドの例では、パラメーターの 1 つとしてモデルの状態を取得する ApplyTo
のオーバーロードを呼び出しています。 このオプションを利用すると、応答内にエラー メッセージを取得できます。 次の例では、test
操作に対する 400 Bad Request 応答の本文を示しています。
{
"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 パッチ ドキュメント サンプルの 1 つをコピーして貼り付けます。
その他のリソース
- IETF RFC 5789 PATCH メソッドの仕様
- IETF RFC 6902 JSON パッチの仕様
- IETF RFC 6901 JSON パッチのパス形式の仕様
- JSON パッチ ドキュメント。 JSON パッチ ドキュメントを作成するためのリソースへのリンクを含みます。
- ASP.NET Core JSON パッチのソース コード
ASP.NET Core