Web API PUT / POST中的部分实体更新

时间:2013-04-22 00:34:32

标签: asp.net-mvc-3 web-applications asp.net-web-api single-page-application

假设您有一个更新文档的存储库方法:

public Document UpdateDocument(Document document)
  {
  Document serverDocument = _db.Documents.Find(document.Id);
  serverDocument.Title = document.Title;
  serverDocument.Content = document.Content;
  _db.SaveChanges();
  return serverDocument;
  }

在这种情况下,实体有两个属性。更新文档时,JSON请求中都需要这两个属性,因此请求PUT /api/folder的主体为

{
  "documentId" = "1",
  "title" = "Updated Title"
}

会返回错误,因为未提供“内容”。我这样做的原因是,即使对于用户不更新的可空属性和属性,强制客户端在请求中指定这些字段似乎更安全,以避免使用nulls服务器端覆盖未指定的字段。

这导致我总是要求PUT和POST请求中的每个可更新属性,即使这意味着为这些属性指定null。

这很酷吗,还是有一种我还没有学到的模式/练习可能会通过只发送线路上需要的内容来促进部分更新?

2 个答案:

答案 0 :(得分:16)

API设计的最佳实践是使用HTTP PATCH进行部分更新。 事实上,像你这样的用例就是IETF首先引入它的原因。

RFC 5789非常准确地定义了它:

  

PATCH 用于将部分修改应用于资源。

     

有必要采用新方法来提高互操作性并防止发生这种情况   错误。 PUT 方法已定义为覆盖资源
  使用完整的新主体,并且无法重复使用以进行部分更改。   否则,代理和缓存,甚至客户端和服务器可能会得到   混淆了操作的结果。 POST已被使用但是   没有广泛的互操作性(对于一个,没有标准的方法   发现补丁格式支持)。

Mark Nottingham撰写了一篇关于在API设计中使用PATCH的精彩文章 - http://www.mnot.net/blog/2012/09/05/patch

在你的情况下,那将是:

  [AcceptVerbs("PATCH")]
  public Document PatchDocument(Document document)
  {
      Document serverDocument = _db.Documents.Find(document.Id);
      serverDocument.Title = document.Title;
      serverDocument.Content = document.Content;
      _db.SaveChanges();
      return serverDocument;
  }

答案 1 :(得分:2)

  

这很酷,还是有一种我没学过的模式/练习   然而,通过仅发送内容可以促进部分更新   电线需要吗?

执行POSTPUT的一个好方法是仅包含该特定请求所需的值。在做UpdateDocument时你应该问自己“这里应该做什么”?如果您在该对象上有一百个字段,则需要更新所有字段或仅更新其中的一部分。你真的想做什么“行动”?

让我们举例说明这些问题,比如我们有一个User对象,其中包含以下字段:

public class User {
    public int Id {get;set;}
    public string Username {get;set;}
    public string RealName {get;set;}
    public string Password {get;set;}
    public string Bio {get;set;}
}

然后你有两个用例:

  1. 更新用户的个人资料
  2. 更新用户密码
  3. 当你做那些你不会做的时候,或者最好有一个可以做到这两点的更新方法。您应该使用以下方法,而不是使用通用UpdateUser方法:

    1. UpdateProfile
    2. UpdatePassword
    3. 接受他们只需要的字段的方法,仅此而已。

      public User UpdateProfile(int id, string username, string realname, string bio) {
      }
      public User UpdatePassword(int id, string password) {
      }
      

      现在问题是:

        

      我有一个“用户操作”允许更新的用例   多个字段,其中一些字段可以从“无输入”   用户,但我不想在我的模型中更新该字段。

      假设用户更新了他/她的个人资料,并为UsernameRealName提供了值,但未提供Bio的值。但是如果它已经有值,你不希望将Bio设置为null或为空。然后,这将成为应用程序业务逻辑的一部分,应该明确处理

      public User UpdateProfile(int id, string username, string realname, string bio) {
          var user = db.Users.Find(id);
          // perhaps a validation here (e.g. if user is not null)
          user.Username = username;
          user.RealName = realname;
          if (!string.IsNullOrEmptyWHiteSpace(bio)) {
              user.Bio = bio;
          }
      }