15

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

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。

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

4

2 回答 2

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;
  }
于 2013-04-22T02:30:15.630 回答
2

Is this cool, or is there a pattern/practice that I haven't learned about yet that might facilitate partial updates by sending only what is needed over the wire?

A good practice of doing a POST or PUT is to only include values that you need for that specific request. In doing the UpdateDocument you should ask yourself what "really should be done here"? If you have a hundred fields on that object do you need to update all of them or only part of them. What "action" are you really trying to do?

Let's have an illustration for those questions, say we have a User object that has the following fields:

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

You then have two use cases:

  1. Update the profile of a User
  2. Update the password of a User

When you do each of those you will not, or it's a good idea to, have one update method that will do both. Instead of having a generic UpdateUser method you should have the following methods:

  1. UpdateProfile
  2. UpdatePassword

Methods that accepts fields that they just need, nothing more, nothing less.

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

Now comes the question:

I have a use case that a "user action" allows for an update on multiple fields where some of the fields can have "no input" from the user but I don't want to update that field in my model.

Suppose a user updates his/her profile and provided values for Username, RealName but not for Bio. But you do not want to set Bio as null or empty if it has a value already. Then that becomes a part of your application's business logic and that should be handled explicitly.

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;
    }
}
于 2013-04-22T01:37:06.843 回答