之前讲了RESTful API的统一资源接口这个约束,里面提到了资源是通过URI来进行识别的,每个资源都有自己的URI。URI里还涉及到资源的名称,而针对资源的名称却没有一个标准来进行规范,但是业界还是有一些最佳实践的。那么我们首先看看这些最佳实践对资源命名是如何建议的。

资源命名

下面让我们来看看RESTful API资源命名的一些最佳实践。

使用名词,而不是动词

一个资源的URI代表的是一个实际上或概念上存在的东西,因此,它应该是名词,所以也就不应该出现动词,动词应该使用HTTP方法来表达。

需求:我们看这样一个需求的例子:“我想获得系统里所有的用户”。

常见错误做法:你可能把API的URI设计成这样:api/getusers。这样的设计是不好的,因为里面出现了一个动词get。

分析:这个句话的主要动词就是“获取”,而想要获取的资源(也就是主要的名词)是“用户”。

正确的做法:需求里面主要的动词应该通过HTTP方法来体现,“获取”对应的HTTP方法就是GET。而“用户”这个资源可以用英文user或者users来表示(是否使用复数一直存在争议,两种方法都行,但你在使用的时候需要保持一致)。所以正确的uri应该是 GET api/user。

人类能读懂

还是上面那个需求:“我想获得系统里所有的用户”。

我们可以把uri设计成 api/u 或者 api/ur。但是这样设计的话,对API的消费者来说非常的不友好,因为不能直观的看出来它到底代表的是什么资源,可能是user,也可能是university。

所以建议的做法是要足够友好,并且比较简短,例如:api/users。

要体现资源的结构关系

假设如果后端API系统里面有若干种资源,而用户这个资源与其它的资源并没有直接的关系,这样的话获取用户资源的uri应该是 api/users。而不是 api/products/users,也不是api/catalogs/products/users,因为user和product或者catalog没有直接的关系。

通过id获取单个用户的uri应该是:api/users/{userId},而不是api/userid/users。

这样写的好处是可以让API具有很好的可预测性和一致性。

需求1:系统里有两类资源,公司(Company)和员工(Employee),它们俩是包含关系,也就是一个公司包含多个员工。现在我想获取某个公司下所有的员工信息。

分析:这里的主要动词还是“获取”,所以我们可以使用HTTP的GET。而这里的资源有两个,分别是公司和员工,而且它们是包含关系:一个公司包含多个员工或者说一个公司是一个员工的集合。所以API的URI在设计的时候需要体现这种包含关系。

常见的错误做法:如果你想获得公司这个资源,我想你现在应该不会出错,uri应该是 api/companies。而想要获取某个公司下的员工,常见的错误做法有:api/employees,api/employees/{companyId}等等。这些设计非常不好是因为它无法体现出Company和Employee之间的结构关系。

建议的做法:需要体现Company和Employee之间的关系,所以uri应该是GET api/companies/{companyId}/employees。这样做直接体现出了Company和Employee之间的结构关系,而且也体现出了一个Company就是一个Employee的集合体。

需求2:我想获取某个公司的某个员工信息。

常见的错误做法:api/employees/{employeeId},api/companies/{employeeId}等等。这些做法都无法体现出Company和Employee之间的关系。

建议的做法:api/companies/{companyId}/employees/{employeeId}。

自定义查询怎么命名

我们经常会遇到这样的需求,比如获取按照某个资源排序后的资源,或者按照某些条件过滤后的资源。这时候应该怎对资源进行命名呢?

需求:“我想获取所有的用户信息,并要求结果是按年龄从小到大进行排列的”。

常见错误的做法:api/users/orderby/age。之前说了,uri里面使用的都应该是名词,如果按照这个uri的结构来看,那么orderby和age就应该是另外两个资源,并且users包含orderby,orderby包含age,这显然是错误的。

建议的做法:api/users?orderby=name,这样设计更合理一些。这里使用了query string作为查询参数进行排序。

例外

有一些需求总是无法满足的达到RESTful的约束。

需求:“我想获取系统里所有用户的数量”。

妥协的做法:我们确实可以先通过 GET api/users来获取系统里所有的用户信息,然后再算出用户的数量,但是这样做也太浪费资源并且效率也太低了。我们也很难使用某个名词来表示这个需求的资源。例如:api/users/totalamountofuser。这样的uri按理说就代表着我们将会获取到一个集合资源,里面是一堆数字,但针对这个需求,我也没有特别好的办法让uri命名完全符合RESTful的约束,所以针对这个需求,我使用的就是这个uri。

Demo

下面我们就来实践一下。打开之前的项目,并建立CompaniesController:

这里有6个地方比较关键,我们挨个看一下:

  1. RESTful API 或者其它Web API的Controller都应该继承于 ControllerBase 这个类(点此查看详细的官方文档),而不是Controller这个类。

    1. Controller类继承于ControllerBase,Controller添加了对视图的支持,因此它更适合用于处理 MVC Web 页面,而不是 Web API。但是如果你的Controller需要同时支持MVC Web页面和Web API,那么这时候就应该继承于Controller这个类。

    2. ControllerBase 类提供了很多用于处理 HTTP 请求的属性和方法。 例如,ControllerBase.CreatedAtAction 返回 201 状态代码。关于ControllerBase的属性和方法的详细列表,请查看官方参考文档

  2. [ApiController]。这个属性是应用于Controller的,它其实并不是强制的,但是它提供了一些帮助,使得Web API的开发体验更好。详细教程请点击 [ApiController]的官方文档。在Controller上面添加了[ApiController]属性之后,就会启用以下行为:

    1. 要求使用属性路由(Attribute Routing)。也就是不能通过Startup的Configure方法统一配置路由模板。这部分的详细介绍请点击:官方文档

    2. 自动HTTP 400响应。也就是Action方法传入的model含有验证错误的时候,自动触发HTTP 400响应。这部分的详细介绍请点击:官方文档

    3. 推断参数的绑定源。它将会推断出Action方法的参数到底来自哪个绑定源,例如[FromBody]、[FromForm]等等。这部分的详细介绍请点击:官方文档

    4. Multipart/form-data 请求推断。使用 [FromForm] 属性批注操作参数时,[ApiController] 属性将应用推断规则,它会推断 multipart/form-data 为请求的内容类型。这部分的详细介绍请点击:官方文档

    5. 错误状态代码的问题详细信息。MVC 会将错误结果(状态代码为 400 或更高的结果)转换为状态代码为 ProblemDetails 的结果。 ProblemDetails 类型基于 RFC 7807 规范,用于提供 HTTP 响应中计算机可读的错误详细信息。这部分的详细介绍请点击:官方文档

  3. 我们需要通过构造函数注入ICompanyRepository,并把它存放在一个只读的字段里面。

  4. 如果注入的ICompanyRepository的实例为null,那么就抛出一个ArgumentNullException。

  5. 想要返回数据结果,我们需要在Controller里面添加一个Action方法。我暂时把它的返回类型写为IActionResult(详细介绍请点击官方文档)。IActionResult里面定义了一些合约,它们可以代表Action方法返回的结果。

  6. 我暂时只想把结果序列化为JSON格式并返回,这里我new了一个JsonResult参考文档),它可以做这项工作。

目前我只做了这几项最基本的工作:创建Controller,注入Repository,创建Action方法并返回结果。下面运行一下看看报了什么错:

这是因为GetCompanies这个Action方法并没有使用属性路由(Attribute Routing)。关于路由这部分,下一篇文章再介绍。

使用ASP.NET Core 3.x 构建 RESTful API - 3.1 资源命名的更多相关文章

  1. 使用ASP.NET Core 3.x 构建 RESTful API - 2. 什么是RESTful API

    1. 使用ASP.NET Core 3.x 构建 RESTful API - 1.准备工作 什么是REST REST一词最早是在2000年,由Roy Fielding在他的博士论文<Archit ...

  2. 使用ASP.NET Core 3.x 构建 RESTful API - 1.准备工作

    以前写过ASP.NET Core 2.x的REST API文章,今年再更新一下到3.0版本. 先决条件 我在B站有一个非常入门的ASP.NET Core 3.0的视频教程,如果您对ASP.NET Co ...

  3. 使用ASP.NET Core 3.x 构建 RESTful API - 3.4 内容协商

    现在,当谈论起 RESTful Web API 的时候,人们总会想到 JSON.但是实际上,JSON 和 RESTful API 没有半毛钱关系,只不过 JSON 恰好是RESTful API 结果的 ...

  4. 使用ASP.NET Core 3.x 构建 RESTful API - 4.2 过滤和搜索

    向Web API传递参数 数据可以通过多种方式来传给API. Binding Source Attributes 会告诉 Model 的绑定引擎从哪里找到绑定源. 共有以下六种 Binding Sou ...

  5. 使用ASP.NET Core 3.x 构建 RESTful API - 3.2 路由和HTTP方法

    ASP.NET Core 3.x 的路由 路由机制会把一个请求的URI映射到一个Controller上面的Action,所以当你发送一个HTTP请求的时候,MVC框架会解析这个请求的URI,并尝试着把 ...

  6. 使用ASP.NET Core 3.x 构建 RESTful API - 3.3 状态码、错误/故障、ProblemDetails

    HTTP状态码 HTTP状态码会告诉API的消费者以下事情: 请求是否执行成功了 如果请求失败了,那么谁为它负责 HTTP的状态码有很多,但是Web API不一定需要支持所有的状态码.HTTP状态码一 ...

  7. 使用ASP.NET Core 3.x 构建 RESTful API - 5.1 输入验证

    说到验证,那就需要做三件事: 定义验证规则 按验证规则进行检查 报告验证的错误.在把错误报告给API消费者的时候,报告里并不包含到底是服务端还是API消费者引起的错误,这是状态码的工作.而通常响应的B ...

  8. 使用ASP.NET Core 3.x 构建 RESTful API - 4.1 面向外部的Model

    Entity Framework Core 使用的 Entity Model 是用来表示数据库里面的记录的. 而面向外部的 model 则表示了要传输的东西.这类 model 有时候叫做 Dto,有时 ...

  9. 使用ASP.NET Core 3.x 构建 RESTful API - 4.3 HTTP 方法的安全性和幂等性

    什么样的HTTP方法是安全的? 如果一个方法不会该表资源的表述,那么这个方法就被认为是安全的. 例如 HTTP GET 和 HTTP HEAD 就被认为是安全的,但需要注意的是,这并不意味着执行GET ...

随机推荐

  1. MySQL视图的优缺点以及如何创建视图

    视图,虚拟表,从一个表或多个表中导出来的表,作用和真实表一样,包含一系列带有行和列的数据 视图中,用户可以使用SELECT语句查询数据,也可以使用INSERT,UPDATE,DELETE修改记录,视图 ...

  2. prototype与 _proto__的关系

    prototype与 __ proto__ 都是在这个过程中催生的产物,我们一会儿马上讨论,在这之...做对象即可,那javascript种究竟是通过什么来明确继承关系的呢. 一.构造函数: 构造函数 ...

  3. libevent环境搭建

    环境 VS2017.ubuntu16.04  官网网址:http://www.linuxfromscratch.org/blfs/view/svn/basicnet/libevent.html 下载 ...

  4. API设计中防重放攻击

    HTTPS数据加密是否可以防止重放攻击? 否,加密可以有效防止明文数据被监听,但是却防止不了重放攻击. 防重放机制 我们在设计接口的时候,最怕一个接口被用户截取用于重放攻击.重放攻击是什么呢?就是把你 ...

  5. 让搭建在 Github Pages 上的 Hexo 博客可以被 Google 搜索到

    title: 让搭建在Github Pages上的Hexo博客可以被Google搜索到 date: 2019-05-30 23:35:44 tags: 配置 --- 准备工作 搭建好的博客 npm & ...

  6. # webpack 打包工具(vue)

    vue-webpack 打包工具 我的github iSAM2016 不是教程,是自我总结 目录 webpack.base.conf.js webpack.dev.conf.js webpack.pr ...

  7. day20作业

    1.下面这段代码的输出结果将是什么?请解释. class Parent(object): x = 1 class Child1(Parent): pass class Child2(Parent): ...

  8. django-URL重定向(八)

    HttpResponseRedirect()不常用 redirect(to,permanent=False,*args,**kwargs) to:指重定向的位置,可以是视图,也可以是url地址,也可以 ...

  9. UART和RS232/RS485的关系是什么?

    串口通讯是电子工程师和嵌入式开发工程师面对的最基本问题,RS232则是其中最简单最常用的通讯方式.但是初学者往往搞不清有关的名词如UART和RS232或RS485之间是什么关系,因为它们经常被放到语句 ...

  10. tomcat的虚拟路径的配置

    在一些项目中有时候需要把一些文件(例如图片.视频)存储在硬盘上的,如果是把文件在放在硬盘上的话,怎么才能访问到这些文件昵. 好了.下面就为大家讲讲如何利用tomcat 虚拟路径访问硬盘上的文件.总共有 ...