【问题标题】:Partial Entity Updates in WebAPI PUT/POSTWebAPI PUT/POST 中的部分实体更新
【发布时间】:2013-04-22 00:34:32
【问题描述】:

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

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

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

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

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

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

这很酷吗,或者是否有一种我尚未了解的模式/实践可以通过仅通过网络发送所需的内容来促进部分更新?

【问题讨论】:

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


    【解决方案1】:

    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;
      }
    

    【讨论】:

    • 该示例不适用于 Content-Type: application/json-patch {"replace": "/count", "value": 5},除非有操作过滤器或媒体类型格式化程序将补丁模式转换为对象
    • 这个答案是完全不切实际的。如果您想更新Document 中的其他几个字段怎么办?您如何知道哪些字段需要更新?空标题意味着删除或不更新?如果没有正确的补丁格式,就不可能弄清楚这些事情。 AFAIK,不支持 JSON-Patch ASP.Net 世界。不过,我很想被证明是错误的。哦,我是你文章的忠实粉丝! :)
    • 忘了提及,OData 的Delta 可能会有所帮助,尽管我不确定您是否可以在不与整个 OData 结合的情况下使用它。
    【解决方案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. 更新用户密码

    当您执行其中的每一项时,您不会,或者最好使用一种更新方法来同时执行这两项操作。您应该使用以下方法,而不是使用通用的 UpdateUser 方法:

    1. UpdateProfile
    2. UpdatePassword

    接受他们只需要的字段的方法,仅此而已。

    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;
        }
    }
    

    【讨论】:

    • 这些方法签名不适用于 ASP.NET Web API,因为:1) 除非使用 [FromBody] 属性明确指示,否则它不会在主体中查找原始参数, 2) 无论如何它只能从 body 绑定一个对象
    • 根据 HTTP 规范,您不得使用 PUT 进行部分更新。不过你可以用 POST 做你想做的事。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-08
    • 1970-01-01
    • 1970-01-01
    • 2013-01-02
    • 1970-01-01
    • 2014-01-18
    相关资源
    最近更新 更多