用ASP.NET Core 2.0 建立规范的 REST API -- GET 和 POST

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

本文介绍的是使用ASP.NET Core建立Richardson成熟度为2级的伪RESTful web API, 本文介绍的是GET和POST.

使用的项目是(右键另存为, 然后把后缀名改为zip): https://images2018.cnblogs.com/blog/986268/201805/986268-20180516191053536-1701412182.jpg

RESTful API 资源 (Resource) 的命名指导规范

首先, 资源应该使用名词, 它是个东西, 不是动作.

例如:

  • api/getusers 就是不正确的.
  • GET api/users 就是正确的
  • GET api/users/{userId}.

所以资源应该使用的是名词.

如果是非分层结构的资源, 那么它不应该这样命名: api/xxx/xxx/users, 而应该使用 api/users.

如果是单个资源, 不应该这样 api/id/users, 而应该是 api/users/{userId}.

(资源名是否复数还是根据个人习惯吧).

命名应该可以体现资源的结构

例如 api/department/{departmentId}/emoloyees, 这就表示了department (部门)和 员工(employee)之前是主从关系.

而 api/department/{departmentId}/emoloyees/{employeeId}, 就表示了该部门下的某个员工.

而过滤, 排序等不是资源, 所以这样写 api/users/orderby/username 是不正确的.

过滤排序这类的参数是可以作为查询参数传递进来的, 正确的写法应该是: api/users?orderby=username.

但是有时候, RPC风格的方法调用很难映射成规范的资源命名, 所以有时可以打破规范 例如 api/users/{userId}/totalsalaries.

应该使用什么类型作为ID

如果使用int型作为ID的话, 大部分时候是没有问题的, 但是如果您使用的数据库的ID是自增整型的, 如果你替换数据库了, 然后把原有数据迁移到新数据库了, 那么现有数据的ID就会发生变化, 那么相当于所有的资源的地址发生了变化, 这就违反了这个:

资源的URI应该永远都是一样的.

所以GUID应该作为ID来使用. (但是我为了省事, 还是使用自增int作为ID吧?).

使用GUID作为主键的好处就是:

  • 可以切换数据库
  • 一定层度上隐藏了内部实现细节

通过HTTP方法与资源交互

针对项目里的Country这个资源, 请参考下面这个列表:

这里GET可以理解为获取(查询)资源, POST为添加资源, PUT为整体更新资源, PATCH为局部更新资源, DELETE为删除资源.

这里需要提的是后两个:

  • HEAD: 和GET差不多, 但是它不应该返回响应的body, 所有没有响应的payload. 它主要使用来获取资源的一些信息, 例如查看资源是否可用等.
  • OPTIONS: 它是用来查询某个资源URI的可交互方式有哪些, 换句话说就是, 使用它可以知道某个URI是否可以执行GET或者POST动作, 这些结果通常是在响应的Headers里面而不是body里, 所以也没有响应的payload.

建立Controller

首先需要建立一个CountryController:

注意在CountryController上面标注的[Route]属性标签,它的值是整个Controller下所有的Action的路由前缀,可以写成固定的地址,也可以写成"api/[controller]", 其中[controller]这部分会变成这个Controller的名字,这里也就是"api/country".

如果使用[controller]的话,如果Controller重构后名字改了,那么该Controller的路由地址也就是资源的地址也就改了,这样很不好,所以建议还是写成固定的地址不要使用[controller]。

GET 资源

GET 所有的Country:

AutoMapper的使用方法这里就不介绍了

 GET 一个Country:

这两个方法里返回的都是JsonResult,这看起来没什么问题,因为我们想要的就是JSON格式的结果。以第二个方法为例,使用POSTMAN测试,如果能查询到数据:

这是没有问题的,但是如果查询一个不存在的资源:

这就有问题了,如果查询不到资源,那么返回的应该是404 NOF FOUND 而不是200 OK.

状态码

状态码是非常重要的,因为只有状态码会告诉API的消费者:

  • 请求是否如预期的成功,或者失败
  • 如果出现了错误,谁该为这个错误负责

下面再列举一下web API会用到的状态码:

200级别,表示成功:

  • 200 - OK
  • 201 - Created,表示资源创建成功了
  • 204 - No content,成功执行,但是不应该返回任何东西

400级别,表示客户端引起的错误:

  • 400 - Bad request,表示API的消费者发送到服务器的请求是错误的
  • 401 - Unauthorized,表示没有权限
  • 403 - Forbidden,表示用户验证成功,但是该用户仍然无法访问该资源
  • 404 - Not found,表示请求的资源不存在
  • 405 - Method not allowed,这就是当我们尝试发送请求给某个资源时,使用的HTTP方法却是不允许的,例如使用POST api/countries, 而该资源只实现了 GET,所以POST不被允许
  • 406 - Not acceptable,这里涉及到了media type,例如API消费者请求的是application/xml格式的media type,而API只支持application/json
  • 409 - Conflict,表示该请求无法完成,因为请求与当前资源的状态有冲突,例如你编辑某个资源数据以后,该资源又被其它人更新了,这时你再PUT你的数据就会出现409错误;有时也用在尝试创建资源时该资源已存在的情况。
  • 415 - Unsupported media type,这个和406正好返回来,比如说我向服务器提交数据的media type是xml的,而服务器只支持json,那么就会返回415
  • 422 - Unprocessable entity,表示请求的格式没问题,但是语义有错误,例如实体验证错误。

500级别,服务器错误:

  • 500 - Internal server error,这表示是服务器发生了错误

回到刚才的那两个方法,默认情况下 JsonResult会返回200 OK状态码,可以去修改JsonResult以支持其它的状态码。但是Controller里提供了一些帮助方法返回IActionResult并指定特定的状态码,针对200,就是Ok()方法。

这时就不需要手动返回JsonResult了。

这里需要注意的是,针对集合的内容协商,如果集合是空的,也不应该返回404,因为这个Country资源是存在的,只不过它的内容是空的而已。

然后看一下GET 特定单个资源:

针对单个资源,如果没有找到,就需要返回404 Not Found,这时就可以使用Controller的帮助方法 NotFound().

处理异常

当Action发生异常的时候,默认情况下ASP.NET Core会返回500:

但还是自己处理一下比较好,可以在Action里面使用try catch:

这里由于是服务器的错误,所以应该返回500状态码 Internal Server Error。

注意这里不应该返回Exception,因为这是程序的内部实现细节,再说它对客户来说也没什么用。

此外,我们还可以全局处理异常。

在Startup里面的Configure方法:

使用app.UseExceptionHandler(),里面可以自定义一些逻辑。如果这地方代码比较多的话,可以把它封装成一个扩展方法,然后使用app.Usexxx的形式调用。

回头我把Action里面的try catch去掉试试,但是这里要注意把环境变量ASPNETCORE_ENVIRONMENT的值改成Production(其实不是Development就可以):

GET 父子关系的资源

这是一个典型的情景,一个国家包含多个城市,这就是父子关系。

首先看一下domain model:

这个应该很简单。

此外还要建立CityResource,Repository和IRepository,注册配置,种子数据等等,这些就不贴了。

下面建立CityController

前面提到过,针对父子、主从关系的资源,其子资源的路由地址应该是上面这样的,由于该Controller下所有的Action的路由前缀都是一样的,所以把这个路由放到了Controller级别作为所有Action的前缀。

而GET方法本身比较简单,没什么说的,里面涉及的一些方法请自行编写。

看看运行结果:

如果找不到Country,则返回404:

下面GET 单个city:

注意,单个资源找不到就应该返回404,而空集合怎不是,这个前面也提过。

找到资源的结果:

找不到country或者city的时候都应该返回404,就不贴图了。

内容协商

简单来说就是,如果资源支持多种展现格式,那么消费者可以选择它想要的格式

这里就要用到media type,它可以通过请求的Accept Header来传递,常见的有:

application/json 和 application/xml...等等

在没有指定Accept Header的情况下,就该返回一个默认的格式,在ASP.NET Core 2.0里面就是application/json。

当请求的media type不可用的时候,并且消费者不支持默认格式,这时服务器就应该返回 406 Not Acceptable 状态码。

ASP.NET Core 支持输出和输入两种格式化器

输出的media type在accept header里面,而输入的media type在content-type header里面

看一下当前的情况,请求的Accept Header为application/json时:

请求的Accept Header为application/xml时:

它们返回的都是json格式的。

因为服务器(项目)现在不支持xml,所以返回了默认的json格式,但严格来说,这样做不正确,所以需要处理一下。

在Startup里,ConfigureServices方法:

把这个ReturnHttpNotAcceptable属性设为true,如果想要的格式不支持,那么就会返回406 Not Acceptable:

不指定Accept Header的情况下就返回默认的json格式:

下面,为项目添加Xml输出格式的支持:

再试试:

这时就成功的返回了xml。

创建资源

首先了解一下方法的安全性和幂等性。

安全性是指方法执行后并不会改变资源的表述。

幂等性是指方法无论执行多少次都会得到同样的结果。

下面是HTTP方法的安全性和幂等性列表:

参考这个列表可以帮助决定在某种情况下用哪种HTTP方法。

下面看看创建Country的代码:

这个代码很简单,数据是从请求的body带进来的。

需要注意的是返回什么,如果POST操作执行成功的话,标准的做法是返回201 Created 状态码。

在这里就可以使用CreatedAtRoute() 这个方法,它允许响应里带着Location Header,在这个Location Header里包含着一个uri,通过这个uri就可以GET到我们刚刚创建好的资源(Country)。

这个方法的第一个参数是一个路由名,使用这个路由名可以用来生成刚才提到的uri。在本例里,这个路由名应该对应的是GetCountry这个Action方法,所以为这个Action添加路由名:

这样就和Post方法返回中用到的路由名一致了,第二个参数是一个匿名类里面有个属性id,它会编程路由里的参数,最后一个参数是响应会返回的数据。

下面进行测试,发送请求的时候别忘了设置Content-type为applicaiton/json:

然后是数据:

然后发送请求,查看响应的body部分:

再看响应的header:

这里可以看到Location Header的uri,通过这个uri,你就可以GET到这个刚刚创建的Country资源,这里我就不测试了。

如果再次执行这个POST操作,看看结果:

这次返回的数据的id为6,与前面不一样,所以POST不是幂等的,它每次执行后的结果是不一样的。

创建子资源

Country的创建做完了,现在可以创建City了。

这个跟上面的差不多,只不过注意需要一下路由的参数即可。

测试:

同时创建父子资源

这是个常见的需求,一个Country和它下属的Cities同时被传递进来,然后在Action里一同创建。

首先需要修改CountryAddResource:

然后,就没有然后了,所有的映射操作都交给AutoMapper和EntityFramework Core了。。

测试:

然后GET这两个Cities:

创建集合资源

这次我要一次性添加一个集合的Countries。

由于Country的集合相当于是另外一种资源,所以可以把它放到单独的Controller里面,不放也没问题。

这个其实也没什么特别的,注意传进来的参数是IEnumerable。为了方便,暂时先返回OK()。

测试:

OK, 下面解决返回的问题.

我们要返回的是CreatedAtRoute方法, 由于里面要包含可以返回该集合资源的路由地址, 所以需要创建一个Action, 它的参数应该是POST方法返回数据的Id的集合. 但是由于路由参数不支持集合形式, 只能以字符串形式传递, 所以可以做成这样的路由参数: api/xx/(1,2,3,4,5).

而Action方法呢, 接受的参数应该是Id的集合, 应该是一个集合类型, 所以我们可以使用ModelBinder把id字符串转化为id的集合:

然后, 还需要对应这个POST Action 做一个GET集合的Action 方法:

这个Action所期待的参数类型是Id的集合, 而实际传入的是id的字符串, 通过ArrayModelBinder来实现转化.

最后修改POST方法的返回:

测试一下:

然后再GET这个链接:

OK

如果POST到单个资源的地址

如果POST到这个地址 http://localhost:5000/api/countries/{id}, 

那么, 如果该id的资源不存在, 则应该返回404;

如果该id的资源存在, 则应该返回409 Conflict.

(POST不是幂等性的, 它无法多次请求都产生同样的结果).

测试一下id的资源不存在的情况:

在测试一下Id的资源存在的情况:

还是404, 这个不行, 所以需要手动处理:

看看结果:

OK, 无论是Id存在的资源还是不存在的资源都会返回正确的状态码.

支持输入其它类型的Content-Type

之前讲过如何返回xml的格式, 下面介绍一下如何使用xml格式进行请求, 首先在Startup.cs里面添加这个:

然后, 需要把请求的Content-Type设为application/xml:

我就不适用xml数据进行测试了.

这次先到这, 随后会写DELETE, UPDATE, PATCH.

本文的源码地址: https://github.com/solenovex/ASP.NET-Core-2.0-RESTful-API-Tutorial

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

29 条运维工程师必会实用 Linux 命令

虽然Linux发行版支持各种各样的饿GUI(graphical user interfaces),但在某些情况下,Linux的命令行接口(bash)仍然是简单...

32990
来自专栏大内老A

WCF技术剖析(卷1)之目录

第1章  WCF简介 (WCF Overview)     1.1  SOA基本概念的和设计思想        1.2  WCF是对现有Windows平台下...

23280
来自专栏草根专栏

用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识 + 项目准备

REST 是 Representational State Transfer 的缩写. 它是一种架构的风格, 这种风格基于一套预定义的规则, 这些规则描述了网络...

1.2K60
来自专栏人人都是极客

Linux 程序编译过程的来龙去脉

大家肯定都知道计算机程序设计语言通常分为机器语言、汇编语言和高级语言三类。高级语言需要通过翻译成机器语言才能执行,而翻译的方式分为两种,一种是编译型,另一种是解...

27130
来自专栏JavaEdge

大道缓存1 缓存特征2 缓存介质3 缓存分类和应用场景缓存实战

用户请求从界面(浏览器/App)到网络转发、应用服务再到存储(数据库或文件系统),然后返回到界面呈现内容。

13110
来自专栏SDNLAB

POF技术分享(二):POF交换机源码结构

一:函数调用关系图 POF交换机先会进行基本配置的初始化、交换机资源初始化等,然后开启交换机与控制器通信进程,建立连接进行通信,最后开启基于流表的数据包匹配与处...

38570
来自专栏微信公众号:Java团长

Java Web从前端到后台常用框架介绍

Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解...

25330
来自专栏Linux驱动

QT-第一个程序 Hello QT , 以及QT creator介绍

第一个程序 - Hello QT 首先写main.cpp: #include <QApplication> #include <QMainWindow> #in...

40870
来自专栏QQ音乐技术团队的专栏

ContentProvider简介

(一) 基础知识 Content Provider属于Android四大组件之一,相比较而言,它更侧重于共享数据。Android的数据存储方式有以下几种:...

33760
来自专栏顶级程序员

Java Web前端到后台常用框架介绍

来源: 小宝鸽 - CSDN博客 链接: http://blog.csdn.net/u013142781/article/details/50922010 一...

62070

扫码关注云+社区

领取腾讯云代金券