web-dev-qa-db-ja.com

Web API 2-PATCHの実装

現在、RESTFul APIを実装するWeb APIを持っています。 APIのモデルは次のようになります。

public class Member
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime Created { get; set; }
    public DateTime BirthDate { get; set; }
    public bool IsDeleted { get; set; }
}

これに似た行を更新するためのPUTメソッドを実装しました(簡潔にするために、関連のないものをいくつか省略しました)。

[Route("{id}")]
[HttpPut]
public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id, 
    [FromBody]Models.Member model)
{
    // Do some error checking
    // ...
    // ...

    var myDatabaseEntity = new BusinessLayer.Member(id);
    myDatabaseEntity.FirstName = model.FirstName;
    myDatabaseEntity.LastName = model.LastName;
    myDatabaseEntity.Created = model.Created;
    myDatabaseEntity.BirthDate = model.BirthDate;
    myDatabaseEntity.IsDeleted = model.IsDeleted;

    await myDatabaseEntity.SaveAsync();
}

PostMan を使用すると、次のJSONを送信でき、すべてが正常に機能します。

{
    firstName: "Sara",
    lastName: "Smith",
    created: '2018/05/10",
    birthDate: '1977/09/12",
    isDeleted: false
}

これをPUTリクエストとしてhttp://localhost:8311/api/v1/Member/12にボディとして送信すると、IDが12のデータ内のレコードがJSONに表示されるものに更新されます。

しかし、私がやりたいことは、部分的な更新を行うことができるPATCH verbを実装することです。サラが結婚したら、このJSONを送信できるようにしたいと思います。

{
    lastName: "Jones"
}

そのJSONだけを送信し、LastNameフィールドを更新して、他のすべてのフィールドをそのままにしておきたいと思います。

私はこれを試しました:

[Route("{id}")]
[HttpPatch]
public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id, 
    [FromBody]Models.Member model)
{
}

私の問題は、これがmodelオブジェクトのすべてのフィールドを返すことです(LastNameフィールドを除くすべてがヌルです)。これは、Models.Memberオブジェクト。私が知りたいのは、JSONリクエストで実際に送信されたプロパティを検出する方法があり、それらのフィールドだけを更新できるかどうかです?

12
Icemanind

PATCH操作は、通常、正確にその理由でPOSTまたはPUT操作と同じモデルを使用して定義されません:nulldon't changeIETFから

ただし、PATCHの場合、囲まれたエンティティには、新しいバージョンを生成するために、Originサーバーに現在存在するリソースを変更する方法を説明する一連の指示が含まれています。

herePATCH提案を見ることができますが、sumarillyは次のとおりです。

[
    { "op": "test", "path": "/a/b/c", "value": "foo" },
    { "op": "remove", "path": "/a/b/c" },
    { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
    { "op": "replace", "path": "/a/b/c", "value": 42 },
    { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
    { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
11
Tipx

これがMicrosoft JsonPatchDocumentの使用に役立つことを願っています。

コントローラーへの.Net Core 2.1パッチアクション:

[HttpPatch("{id}")]
public IActionResult Patch(int id, [FromBody]JsonPatchDocument<Node> value)
{
    try
    {
        //nodes collection is an in memory list of nodes for this example
        var result = nodes.FirstOrDefault(n => n.Id == id);
        if (result == null)
        {
            return BadRequest();
        }    
        value.ApplyTo(result, ModelState);//result gets the values from the patch request
        return NoContent();
    }
    catch (Exception ex)
    {
        return StatusCode(StatusCodes.Status500InternalServerError, ex);
    }
}

ノードモデルクラス:

[DataContract(Name ="Node")]
public class Node
{
    [DataMember(Name = "id")]
    public int Id { get; set; }

    [DataMember(Name = "node_id")]
    public int Node_id { get; set; }

    [DataMember(Name = "name")]
    public string Name { get; set; }

    [DataMember(Name = "full_name")]
    public string Full_name { get; set; }
}

「full_name」および「node_id」プロパティのみを更新する有効なパッチJSonは、次のような操作の配列になります。

[
  { "op": "replace", "path": "full_name", "value": "NewNameWithPatch"},
  { "op": "replace", "path": "node_id", "value": 10}
]

「op」が実行したい操作であることがわかるように、最も一般的なものは「replace」で、新しいプロパティにそのプロパティの既存の値を設定するだけですが、他にもあります。

[
  { "op": "test", "path": "property_name", "value": "value" },
  { "op": "remove", "path": "property_name" },
  { "op": "add", "path": "property_name", "value": [ "value1", "value2" ] },
  { "op": "replace", "path": "property_name", "value": 12 },
  { "op": "move", "from": "property_name", "path": "other_property_name" },
  { "op": "copy", "from": "property_name", "path": "other_property_name" }
]

以下は、リフレクションを使用してC#のPatch( "replace")仕様に基づいて作成した拡張メソッドで、これを使用してオブジェクトをシリアル化してPatch( "replace")操作を実行できます。 httpClient.PatchAsync(endPoint、httpContent)に送信する準備ができているHttpContent(StringContent)を返します。

public static StringContent ToPatchJsonContent(this object node, Encoding enc = null)
{
    List<PatchObject> patchObjectsCollection = new List<PatchObject>();

    foreach (var prop in node.GetType().GetProperties())
    {
        var patch = new PatchObject{ Op = "replace", Path = prop.Name , Value = prop.GetValue(node) };
        patchObjectsCollection.Add(patch);                
    }

    MemoryStream payloadStream = new MemoryStream();
    DataContractJsonSerializer serializer = new DataContractJsonSerializer(patchObjectsCollection.GetType());
    serializer.WriteObject(payloadStream, patchObjectsCollection);
    Encoding encoding = enc ?? Encoding.UTF8;
    var content = new StringContent(Encoding.UTF8.GetString(payloadStream.ToArray()), encoding, "application/json");

    return content;
}

}

Ttは、DataContractJsonSerializerを使用してPatchObjectをシリアル化するために作成したこのクラスも使用することに注意してください。

[DataContract(Name = "PatchObject")]
class PatchObject
{
    [DataMember(Name = "op")]
    public string Op { get; set; }
    [DataMember(Name = "path")]
    public string Path { get; set; }
    [DataMember(Name = "value")]
    public object Value { get; set; }
}

拡張メソッドを使用し、HttpClientを使用してパッチリクエストを呼び出す方法のC#の例:

    var nodeToPatch = new { Name = "TestPatch", Private = true };//You can use anonymous type
    HttpContent content = nodeToPatch.ToPatchJsonContent();//Invoke the extension method to serialize the object

    HttpClient httpClient = new HttpClient();
    string endPoint = "https://localhost:44320/api/nodes/1";
    var response = httpClient.PatchAsync(endPoint, content).Result;

ありがとう

7
Ernest

PATCHを使用した@Tipxの答えはスポットオンですが、おそらく既にお気付きのように、C#のような静的に型付けされた言語で実際にそれを達成するのは簡単なことではありません。

PATCHを使用して単一のドメインエンティティの部分更新セットを表す場合(たとえば、より多くのプロパティを持つ連絡先の姓と名のみを更新する場合) 「PATCH」リクエスト内の各命令をループし、その命令をクラスのインスタンスに適用するラインに沿った何か。

個々の指示の適用は、

  • 命令内の名前に一致するインスタンスのプロパティを見つけるか、予期しないプロパティ名を処理します
  • 更新の場合:パッチで送信された値をインスタンスプロパティに解析し、エラーの処理を試みます。インスタンスプロパティはブール値ですが、パッチ命令には日付が含まれています
  • 静的に型指定されたC#クラスに新しいプロパティを追加できないため、Add命令の処理方法を決定します。 1つのアプローチは、Addは「プロパティの既存の値がnullの場合にのみインスタンスのプロパティの値を設定する」ことを意味するということです

完全な.NET Framework上のWeb API 2の場合、 JSONPatch githubプロジェクト は、このコードを提供する際に突き刺すように見えますが、最近そのリポジトリで多くの開発が行われているようには見えません。 readmeの状態:

これはまだ初期のプロジェクトです。ソースを理解し、いくつかのバグを修正することを気にしない限り、まだ本番環境で使用しないでください。

.NET Coreでは、 Microsoft.AspNetCore.JsonPatch名前空間

かなり便利な jsonpatch.com サイトには、.NETのPatchのいくつかのオプションもリストされています。

  • Asp.Net Core JsonPatch (Microsoft公式実装)
  • Ramone (RESTサービスを使用するためのフレームワーク、JSONパッチ実装を含む)
  • JsonPatch (ASP.NET Web APIにJSONパッチサポートを追加)
  • Starcounter (インメモリアプリケーションエンジン、OTクライアントとサーバーの同期にJSONパッチを使用)
  • Nancy.JsonPatch (NancyFXにJSONパッチサポートを追加)
  • Manatee.Json (JSONパッチを含むすべてのJSON)

私たちの既存のWeb API 2プロジェクトにこの機能を追加する必要があるので、それを行うときに役立つ何か他のものが見つかった場合は、この回答を更新します。

2
tomRedox