Web API 2 - 实现PATCH

Ice*_*ind 14 c# rest asp.net-mvc json asp.net-web-api2

我目前有一个实现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; }
}
Run Code Online (Sandbox Code Playgroud)

我已经实现了一个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();
}
Run Code Online (Sandbox Code Playgroud)

使用PostMan,我可以发送以下JSON,一切正常:

{
    firstName: "Sara",
    lastName: "Smith",
    created: '2018/05/10",
    birthDate: '1977/09/12",
    isDeleted: false
}
Run Code Online (Sandbox Code Playgroud)

如果我将此作为我的身体发送到http://localhost:8311/api/v1/Member/12PUT请求,ID为12的数据中的记录将更新为您在JSON中看到的内容.

我想做的是实现一个PATCH动词,我可以在那里进行部分更新.如果Sara结婚,我希望能够发送这个JSON:

{
    lastName: "Jones"
}
Run Code Online (Sandbox Code Playgroud)

我希望能够只发送JSON并更新JUST LastName字段并保留所有其他字段.

我试过这个:

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

我的问题是,这将返回model对象中的所有字段(除了LastName字段之外所有字段都是空的),这是有道理的,因为我说我想要一个Models.Member对象.我想知道的是,是否有办法检测JSON请求中实际发送了哪些属性,以便我只更新这些字段?

Tip*_*ipx 12

PATCH操作通常不使用同一模型所定义的POSTPUT你怎么一个区分:操作正是出于这个原因null,和don't change.来自IETF:

但是,对于PATCH,随附的实体包含一组指令,这些指令描述了如何修改当前驻留在源服务器上的资源以生成新版本.

你可以在这里查看他们的PATCH建议,但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" }
]
Run Code Online (Sandbox Code Playgroud)

  • 由于 IETF 建议这种格式的误导性暗示而被否决。在查看了 IETF 的“关于”和“联系我们”页面后,您链接到的“此处”网站似乎与 IETF 没有任何关系。 (2认同)

tom*_*dox 7

@Tipx's answer re usingPATCH是正确的,但正如您可能已经发现的那样,实际上在像 C# 这样的静态类型语言中实现这一点是一项重要的练习。

在您使用 aPATCH表示单个域实体的一组部分更新的情况下(例如,仅更新具有更多属性的联系人的名字和姓氏),您需要按照循环执行某些操作'PATCH' 请求中的每条指令,然后将该指令应用于您的类的实例。

应用单个指令将包括

  • 查找与指令中的名称匹配的实例的属性,或处理您不期望的属性名称
  • 对于更新:尝试将补丁中提交的值解析为实例属性并处理错误,如果例如实例属性是 bool 但补丁指令包含日期
  • 决定如何处理 Add 指令,因为您无法向静态类型的 C# 类添加新属性。一种方法是说 Add 的意思是“仅当属性的现有值为 null 时才设置实例属性的值”

对于完整的 .NET Framework 上的 Web API 2,JSONPatch github 项目似乎试图提供此代码,尽管最近该存储库似乎没有太多开发,而且自述文件确实指出:

这仍然是一个非常早期的项目,除非您了解源代码并且不介意修复一些错误,否则不要在生产中使用它;)

.NET Core 上的事情更简单,因为它具有一组功能来支持Microsoft.AspNetCore.JsonPatch命名空间中的这一点。

相当有用的jsonpatch.com站点还列出了 .NET 中 Patch 的更多选项:

我需要将此功能添加到我们现有的 Web API 2 项目中,因此,如果在此过程中发现其他有用的内容,我将更新此答案。


Ern*_*est 7

我希望这有助于使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

节点模型类:

[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; }
}
Run Code Online (Sandbox Code Playgroud)

一个仅更新“ full_name”和“ node_id”属性的有效补丁JSon将是一系列操作,例如:

[
  { "op": "replace", "path": "full_name", "value": "NewNameWithPatch"},
  { "op": "replace", "path": "node_id", "value": 10}
]
Run Code Online (Sandbox Code Playgroud)

如您所见,“ 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" }
]
Run Code Online (Sandbox Code Playgroud)

这是我基于C#中的Patch(“替换”)规范使用反射构建的扩展方法,可用于对任何对象进行序列化以执行Patch(“替换”)操作,还可以传递所需的Encoding,它将返回准备发送到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;
}
Run Code Online (Sandbox Code Playgroud)

}

注意,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; }
}
Run Code Online (Sandbox Code Playgroud)

如何使用扩展方法以及如何使用HttpClient调用Patch请求的AC#示例:

    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;
Run Code Online (Sandbox Code Playgroud)

谢谢