c# - Web API 2 - 实现补丁

标签 c# json asp.net-mvc rest 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; }
}

我实现了一个 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
}

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

不过,我想做的是实现一个 PATCH 动词,我可以在其中进行部分更新。如果 Sara 结婚,我希望能够发送这个 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 请求中实际发送了哪些属性,以便我可以仅更新这些字段?

最佳答案

我希望这有助于使用 Microsoft JsonPatchDocument:

.Net Core 2.1 将补丁操作添加到 Controller 中:

[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”属性的有效 Patch 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(“替换”)规范使用反射构建的扩展方法,您可以使用它来序列化任何对象以执行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;
}

注意到 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;

谢谢

关于c# - Web API 2 - 实现补丁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50215825/

相关文章:

asp.net-mvc - ASP.NET MVC : Partial view not rendering

c# - 插入大文件时"ORA-03135: connection lost contact"

c# - 在 ConcurrentQueue 中尝试出队

c# - 无法使用 oledb 从 csv 读取所有列

javascript - 无法删除具有空值的 JSON 项目

javascript - 通过 JSON 文件创建大型客户端 Javascript 对象数组的替代方案?

javascript - 如何使用 Dropzone.js 在服务器错误响应后触发警报

asp.net-mvc - ASP.NET MVC Razor 串联

javascript - MVC asp.net 服务器端计时器以防止用户编辑计时器

c# - 显示来自 ModelState.Values 的错误