首页 > 代码库 > [译] 在Web API 2 中实现带JSON的Patch请求

[译] 在Web API 2 中实现带JSON的Patch请求

原文链接:The Patch Verb in Web API 2 with JSON

我想在.NET4.6 Web API 2 项目中使用Patch更新一个大对象中的某个字断,这才意识到我以前都没有用过Patch。这是一次难得的学习机会。

我不知道在Web API 2中最好的实现方式是什么,所以我按照惯例,用google搜索"Patch Web API"。我得到的第一条结果是Michael McKenna’s “How to Add JSON Patch Support to Web API”。看起来只要照做就行了,但是我想知道为什么他一定要为此写个解决方案。毫无疑问地,在Web API中Patch是一个非常常见的需求,难道.NET就没有一个原生的方式实现它吗?也许Michael也不知道。

经过一番探索之后,在几乎所有的论坛和博客中都无外乎如下三种方法,却没有一个是我喜欢的。

1. 为每个属性写一个api

有人建议为每个修改写个方法,例如设置“Book.Name”,设置“Book.PageCount”等等。毫无疑问地,这种方式实现起来非常花时间,并且也不好维护,特别是当对象中有很多属性或者有很多对象的时候,简直就是天坑:(。

2. 使用ODATA

很多人建议在项目中包含.NET ODATA类库,只用它的Delta类来实现Patch请求。这看起来有点怪怪的。ODATA和简洁的JSON API比起来是一个完全不同的大家伙。我赶脚ODATA有点笨重,不直观,不优雅。

另外,好像在JSON Web API项目中用ODATA类库更新某些基础类型还有一些问题。 由于ODATA类库是为ODATA格式的数据设计的,这种格式完全不像JSON格式那么优雅。好像还有很多人使用(或者正在尝试使用)ODATA去实现JSON格式的Patch请求,但是好像都不怎么理想。

3. 使用带有可空(nullable)属性的patch类型

假设我们想实现在Book对象中更新PageCount属性。在Book类型中,Name是必须的属性,其它的像Language是可空的。这个解决方法是要创建一个Book的补丁类型,其中PageCount、Name等属性都设置成可空的。如果HTTP请求提供了某个属性的值,那么是给这个属性赋值。如果没有提供值的话,就是不改变这个属性。如果你想清空这个属性的值,那么在请求中提供null。

这好像不是那么糟糕:(,但是当传递过来的数据中的可空的属性为null时就需要在补丁类型中写额外的逻辑。还要为每个需要patch的类型写对应的补丁类型。尽管多次创建和修改早已创建好的类型还不是最坑爹的,但是维护将会是个天坑。

这个方法仅用来设置一个属性还好,如果属性是一个数组,我想在其中增加一个item,那就没办法了,除非把整个数组彻底重写来支持Patch操作。或者再为每个需要操作数组的地方写一个API。难道就没有一个优雅的方法吗?

什么是JSON Patch

经常被提起的ODATA其实并不适合,那我干嘛要非得用ODATA去实现JSON类型的Patch操作呢。最优雅的实现方式是什么?难道就没有JSON Patch标准吗?

铛铛铛!这是JSON Patch的标准: standard RFC6902。用类似于这样的指令"set field x to this value"来解释Patch操作,如此,用一个或者多个指令就能解释说明如何操作对象。它还支持其它多种操作,比如,增加、删除某个需要修补的对象中的数组元素,还有设置嵌套对象的属性等等。‘Replace‘,‘move‘, ‘copy‘ 和 ‘test‘操作都已经被定义好了。有趣的是,‘test‘方法可以检查某个属性是否和目标值相等,应该很有用。

JSON Patch看起来像下面这样:

{"foo":"bar"}

如果我们想增加一个名为"name",值为"Carly"的属性,那么发送的HTTP Patch 请求体如下所示:

[
    {"op":"add","path":"/name","vale":"Carly"}
]

我们可能得到的响应如下:

{
    "foo":"bar",
    "name":"Carly"
}

JSON Patch有自己的MIME类型: application/json-patch+json 。

它差不多解决了其它方法中的所有缺陷,并且更加RESTful :o。URI也可以和对象保持一致,只是请求中需要包含操作数据的指令,但是指令一看就懂,非常清晰。我们可以清晰地使用各种方式去操作属性,它可以将多种操作放在一次请求中。完美!

现在很多人还在提倡2003年以前JSON Patch被提出来之前的方法,其中还有一些错误的言论误导了很多的学习者。非常感谢William Durant,及其一针见血的博文“Please. Don’t Patch Like An Idiot”,是这篇博文给了我启发。我知道它虽然没有促使JSON Patch或者XML Patch被.NET原生支持,甚至都没有在.NET Web API的指南中被提及。但这种方法无疑是一个正确的且优秀的方式。

在.NET Web API 2中如何使用Json Patch

现在我们知道了正确的实现方法,那么代码怎么写呢?参考Michael’s post about JSON API的第一行引用的JSON标准。虽然Michael的博文的主要目的是介绍他的JSON Patch类库!但依然值得学习。
以下是摘来的精华:
1. Nuget 安装: Install-Package JsonPatch -Version 1.0.0
1. 支持MIME类型和格式
public static void CongigureApis(HttpConfiguration config){ config.Formatters.Add(new JsonPatchFormatter()); }
1. 在你的API Controller中实现Patch方法

public void Patch(Guid id, JsonPatchDocument<SomeDto> patchData)
{
//Remember to do some validation, error handling, and all that fun stuff
var objectToUpdate = repository.GetById(id); 
patchData.ApplyUpdatesTo(objectToUpdate); 
repository.Save(objectToUpdate); 
}
执行patch请求的Http请求报文如下所示
PATCH /my/data HTTP/1.1
Host: example.org
Content-Type: application/json-patch+json
 
[
    { "op": "add", "path": "/a/b/c", "value": "foo" }
]

发展趋势

值得注意的是Json Patch类库有一点已知的issues和局限。如果你在项目中用到了它,如果你修复了它的issues或者增加了功能,还请您贡献下您的代码。

Using Michael’s JSONPatch library, there are a few scenarios that might come up. One is preventing patch of specific properties, which should be fairly easy to solve with a custom attribute added to the data model.
(这段话还没看明白)

最近,ASP.NET5好像要支持JSON Patch了.

[译] 在Web API 2 中实现带JSON的Patch请求