1.前言

可以这么说的是,任何一种非强制性约束同时也没有“标杆”工具支持的开发风格或协议(仅靠文档是远远不够的),最终的实现上都会被程序员冠上“务实”的名头,而不管成型了多少个版本,与最初的设计有什么区别。DDD 是如此,微服务是如此,REST 也是如此。

虽然这也不难理解,风格从一开始被创造出来后,便不再属于作者了。所以仍然把你的符合以下标准

  • 满足以资源形式定义定义 Uri
  • 满足以 HTTP 谓词语义增删改查资源
  • 符合命名要求
  • ……

的“不标准” Web API 看作是 RESTful 的,也未尝不可。毕竟,谁在乎呢?

更深层次的讨论参见Why Some Web APIs Are Not RESTful and What Can Be Done About It。什么才是真正的 REST Api 并不是本文的重点(Github Rest API v3),笔者在后文讨论的具体实现,也只是符合目前流行的“RESTful”直觉设计。

2. HTTP 谓词

谓词 释义 幂等性 安全性
HEAD 用于获取资源的 HTTP Header 信息
GET 用于检索信息
POST 用于创建资源
PUT 用于更新或替换完整资源或批量更新集合。对于没有 Body 的 PUT 动作,请将 Content-Length 设置为 0
DELETE 用于删除资源
PATCH 用于使用部分 JSON 数据更新资源信息(在一个请求里可搭载多个动作)。PATCH 是一个相对较新的 HTTP 谓词,在客户端或服务器不支持 PATCH 动作时,也可以使用 Post/Put 更新资源

3. PATCH & JSON Patch

结合上述 HTTP 谓词,通常情况下,更新部分资源的部分数据时,有以下四种做法:

  1. 使用 PUT 谓词, 尽可能使用完整对象来更新资源(即根本不使用 PATCH )。
  2. 使用 JSON Merge Patch 更新部分资源的部分数据(需要使用指定 MIME application/merge-patch+json 来表示)。
  3. 使用 PATCH 谓词和 JSON Patch(需要使用指定 MIME application/json-patch+json 来表示)
  4. 如果请求不以 MIME 的语义定义的方式修改资源,使用具有合理描述的 POST 谓词。

我相信大部分系统中,采取的都是第1种和第4种做法,而本文的主题则是第3种做法。

RFC 5789(PATCH method for HTTP) 中,有一个关于 PATCH 请求的小例子:

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 100 [description of changes]

[description of changes] 代表对目标资源的一系列操作,而JSON Patch则是描述操作的文档格式。


// 示例 json 文档
{
"a":{
"b":{
"c":"foo"
}
}
} // JSON Patch 操作
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]

在这个JSON Patch的例子中,op代表操作类型,frompath代表目标 json 的层级路径,value代表操作值。相关语义想必大家都能直接读出来,更多的信息请参考What is JSON Patch?RFC JSON Patch

示例应用

示例程序引入了swaggerMongoDBdocker-compose等功能,关于 JsonPatch 的部分则使用微软官方的 JsonPatch 编写,该库支持addremovereplacemovecopy方法,实现并不困难。实际使用时,直接以JsonPatchDocument<T>作为包装即可。

MongoDB 客户端推荐注册为单例。

public interface IMongoDatabaseProvider
{
IMongoDatabase Database { get; }
} public class MongoDatabaseProvider : IMongoDatabaseProvider
{
private readonly IOptions<Settings> _settings; public MongoDatabaseProvider(IOptions<Settings> settings)
{
_settings = settings;
} public IMongoDatabase Database
{
get
{
var client = new MongoClient(_settings.Value.ConnectionString);
return client.GetDatabase(_settings.Value.Database);
}
}
} /* Startup/ConfigureServices.cs */
public void ConfigureServices(IServiceCollection services)
{

services.AddSingleton<IMongoDatabaseProvider, MongoDatabaseProvider>();

}

appsettings.json文件中的数据库配置部分则为:

{
"ConnectionString": "mongodb://mongodb",
"Database": "ExampleDb"
}

docker-compose.yml对 web 应用和 MongoDB 的配置如下:

version: '3.4'

services:
aspnetcorejsonpatch:
image: aspnetcorejsonpatch
build:
context: .
dockerfile: AspNetCoreJsonPatch/Dockerfile
depends_on:
- mongodb
ports:
- "8080:80"
mongodb:
image: mongo
ports:
- "27017:27017"

启动时,定位到docker-compose.yml所在文件夹,运行docker-compose up,然后在浏览器访问localhost:8080/swagger,应用在启动后会自动创建ExampleDb数据库并插入一条数据。笔者也写了一个获取信息的接口/api/Persons,返回值如下:

[
{
"name": "LeBron James",
"oId": "5af995a5b8ea8500018d54b7"
}
]

然后再使用返回的oId请求/api/Persons/{id}UpdateThenAddThenRemoveAsync)接口,body的 JsonPatch 描述则用:

/* body */
[
{
"value": "Daby",
"path": "FirstName",
"op": "replace"
},
{
"value": "Example Address",
"path": "Address",
"op": "add"
},
{
"path": "Mail",
"op": "remove"
}
] /* PersonsController.cs */
[HttpPatch("{id}")]
public async Task<PersonDto> UpdateThenAddThenRemoveAsync(string id,
[FromBody] JsonPatchDocument<Person> personPatch)
{
var objectId = new ObjectId(id); var person = await _personRepository.GetAsync(objectId); personPatch.ApplyTo(person); await _personRepository.UpdateAsync(person); return new PersonDto
{
OId = person.Id.ToString(),
Name = $"{person.FirstName} {person.LastName}"
};
}

其他相关代码另请查阅。不过需要再提一点的是,Visual Studio 15.7 版本对docker-compose.yml的文本语法解析有些问题,详见MSBuild failing to parse a valid compose file,比如以下代码将无法编译:

environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${MONGODB:-mongodb://mongodb}
- Database=ExampleDb

参考文献

  1. JSON Patch
  2. Github v3 API

JSON Patch的更多相关文章

  1. 【ASP.NET Core】JSON Patch 使用简述

    JSON Patch 是啥玩意儿?不知道,直接翻译吧,就叫它“Json 补丁”吧.干吗用的呢?当然是用来修改 JSON 文档的了.那咋修改呢?比较常见有四大操作:AMRR. 咋解释呢? A—— Add ...

  2. 如何在ASP.NET Core中使用JSON Patch

    原文: JSON Patch With ASP.NET Core 作者:.NET Core Tutorials 译文:如何在ASP.NET Core中使用JSON Patch 地址:https://w ...

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

    原文链接:The Patch Verb in Web API 2 with JSON 我想在.NET4.6 Web API 2 项目中使用Patch更新一个大对象中的某个字断,这才意识到我以前都没有用 ...

  4. 用ASP.NET Core 2.0 建立规范的 REST API -- DELETE, UPDATE, PATCH 和 Log

    本文所需的一些预备知识可以看这里: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblogs.com/cgzl/p/9019314 ...

  5. streamsets http client && json parse && local fs 使用

    streamsets 包含了丰富的组件,origin processer destination 测试例子为集成了http client 以及json 处理 启动服务 使用docker 创建pipel ...

  6. kubectl 之 patch 命令

    patch命令 kubectl patch — Update field(s) of a resource using strategic merge patch Synopsis kubectl p ...

  7. Kubernetes官方java客户端之七:patch操作

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. Json文件解析(下)

    Json文件解析(下) 代码地址:https://github.com/nlohmann/json   从STL容器转换 任何序列容器(std::array,std::vector,std::dequ ...

  9. javaScript系列 [09]-javaScript和JSON (拓展)

    本文输出JSON搜索和JSON转换相关的内容,是对前两篇文章的补充. JSON搜索 在特定的开发场景中,如果服务器端返回的JSON数据异常复杂(可能超过上万行),那么必然就有对JSON文档进行搜索的需 ...

随机推荐

  1. (二十七)QQ好友列表的实现

    QQ好友列表通过plist读取,plist的结构为一组字典,每个字典内有本组的信息和另外一组字典代表好友. 要读取plist,选择合适的数据结构,例如NSArray,然后调用initWithConte ...

  2. 网站开发进阶(二十五)js如何将html表格导出为excel文件

    js如何将html表格导出为excel文件        赠人玫瑰,手留余香.若您感觉此篇博文对您有用,请花费2秒时间点个赞,您的鼓励是我不断前进的动力,共勉! jsp页面数据导出成excel的方法很 ...

  3. VisualSVN Server安装过程

     运行VisualSVN-Server-2.7.3.msi程序, 如下图 点击Next, 下一步 选中 I accept选项, 点击Next, 下一步 选择默认配置, 服务和控制台组件方式, 点击 ...

  4. 【Qt编程】Qt学习之状态栏

    QStatusBar类提供一个水平条来显示状态信息.所谓状态信息,拿个简单的例子来说,当你在word中编辑时,左下角就会出现页面.字数等等信息.状态信息可以分为三类:临时信息,如一般的提示信息:正常信 ...

  5. 网站开发进阶(十二)JS实现打印功能(包括打印预览、打印设置等)

    JS实现打印功能(包括打印预览.打印设置等) 绪 最近在进行项目开发时,需要实现后台管理端打印功能,遂在网上一阵搜索,搜到了很多相关的文章.其中绝大部分文章都是使用的Lodop5.0(Web打印和套打 ...

  6. Mybatis源码之RoutingStatementHandler

    /** * @author Clinton Begin */ public class RoutingStatementHandler implements StatementHandler { pr ...

  7. ZooKeeper 会话超时

    1.会话概述 在ZooKeeper中,客户端和服务端建立连接后,会话随之建立,生成一个全局唯一的会话ID(Session ID).服务器和客户端之间维持的是一个长连接,在SESSION_TIMEOUT ...

  8. ELF 文件 动态链接 - 地址无关代码(GOT)

    Linux 系统中,ELF动态链接文件被称为 动态共享对象(DSO,Dynamic Shared Object),简称共享对象 文件拓展名为".so" 动态链接下 一个程序可以被分 ...

  9. 关于并发下内存及CPU使用情况的思考

    鉴于昨天的文章<<使用Interlocked在多线程下进行原子操作,无锁无阻塞的实现线程运行状态判断>>里面有一个封装好的无锁的类库可以判断并发下的结束状况,我们可以完成并发时 ...

  10. vue2.0-基于elementui换肤[自定义主题]

    0. 直接上 预览链接 vue2.0-基于elementui换肤[自定义主题] 1. 项目增加主题组件 在项目的src/components下添加skin文件夹 skin文件获取地址 2. 项目增加自 ...