RPC
RPC(Remote Procedure Call),远程过程调用),这种RPC形式的API组织形态是类和方法的形式。所以API的请求往往是一个动词用来标识接口的意思,比如 https://xxxx/GetStudent?id=1 和 https://xxxx/AddStudent 这种风格,并且往往没有规范需要我们去查看接口定义文档。HTTP方法基本只用GET和POST,没有使用HTTP的其它谓词设计比较简单。
Rest
Rest:按照Http的语义来使用HTTP协议的一种风格,Rest全称Representational State Transfer(表现层状态转换)。他是一种规范或者设计风格而不是特别的技术。REST形式的API组织形态是资源和实体,请求的路由可以看出对资源的访问,规范统一接口自解释。
比如 https://xxxx/Student/1 用Get方法调用就是获取编号为1的学生。 https://xxxx/Student/1 用Delete调用就是删除编号为1的学生,用delete调用就是删除该学生。
在HTTP中这些调用方法GET、POST、PUT、PATCH、DELETE 即HTTP谓词。GET用来获取资源,POST用来新建资源,PUT用来更新指定资源,PATCH用来批量更新资源,DELETE用来删除资源,通过谓词来表示请求动作或者意图,通过url定位资源。
在请求中GET、PUT、DELETE 请求是幂等的,也就是说可以重试请求。而POST不是幂等,因为POST意思是添加数据。
在Rest风格中使用状态码来标识返回结果,其中常用200、201、400、401、404、410、500等。
ASP.NET Core WebAPI接口
在ASP.NET WebAPI中我们也能看到Rest的风格,理想很丰满,显示很骨干。如果我们严格的按照Rest风格设计接口的话,需要对技术人员有很高的要求,需要去划分不同业务不同的资源定位,而且有些业务也找不到准确的谓词去定义,响应状态码有限无法表达准确的意思,或者是时间上来不及等等原因。
而且这种方式更符合国外语言表达的方式,不太适合我国宝宝体质。
所以我们在设计接口的时候不用非要用Rest风格,我们可以靠近或者在特定的更适合使用Rest接口系统中使用。
本篇我们结合Rest看下接口如何设计和交互。
- 在http接口请求中有三种方式传递参数或者数据。
- URL:资源定位,也就是Rest风格,在请求的url中包含信息,比如https://xxxx/Student/1 1就是学生编号。
- QueryString: URL之外的额外信息,比如RPC中https://xxxx/GetStudent?id=1 id=1就是QueryString
- 请求报文体:供PUT、POST提交提供数据,请求体有多种格式application/x-www-form-urlencoded、multipart/form-data、application/json、text/plain、application/xml。
- 返回状态码
在RPC中Post请求我们习惯如果请求已经在服务器处理,不管处理结果是否正确,我们都返回200状态码。然后在返回数据中用其它信息来标识业务结果。比如{code:1,msg:"成功"}
或者{code:0,msg:"失败"}
。
而在Rest 中Post通常用201返回新增成功,delete 删除的数据不存在返回404,但是404大家知道可能也许是url错误,所以表诉不清。
因此我们在实际设计中可能会进行Rest裁剪,我们既使用RPC的返回结果,同时多用准确的状态码,不用什么都返回200。
使用RPC风格,尽量使用合理谓词,不知道使用什么谓词的时候就用POST,Get Delete参数尽量用资源定位URL,业务错误服务端返回合适的状体吗,不知道返回什么就返回400,如果请求处理成功就用200同时返回结果数据。
在上一篇中遗留的这个问题 .net 温故知新【11】:Asp.Net Core WebAPI 入门使用及介绍
所以我们在Controller中Route配置为[Controller]则不管方法接口名称是什么,仍然以Rest的方式访问。
[Route("[controller]")]
[ApiController]
public class RestCutController : ControllerBase
{
[HttpGet]
public IEnumerable<string> GetStudents()
{
//获取所有学生
return new string[] { "student1", "student2" };
}
[HttpGet("{id}")]
public string GetStudent(int id)
{
//获取id的学生
return "student"+id;
}
[HttpPost]
public void PostStudent([FromBody] string value)
{
//新增
}
[HttpPut("{id}")]
public void PutStudent(int id, [FromBody] string value)
{
//修改
}
[HttpDelete("{id}")]
public void DeleteStudent(int id)
{
//删除id学生
}
}
当我们修改Rout按照RPC方式,[Route("[controller]/[action]")]
运行后发现swagger展示的接口方式就改变了。并且保留了参数URL的方式。
关于返回状态码的问题可以有两种方式,一种是直接在ControllerBase.Response
响应中指定返回状态码。
[HttpDelete("{id}")]
public string DeleteStudent(int id)
{
//删除id学生
if (id == 1)
{
return "删除成功";
}
else {
Response.StatusCode = 404;
return "未找到!";
}
}
另外一种方式就是返回泛型ActionResult<string>
,其中OK
和NotFound
是继承自ActionResult
然后隐式转换到泛型,也可以直接返回IActionResult或者ActionResult但是类型不确定这样swagger文档就不会解析出返回值,所以我们用ActionResult泛型。
[HttpDelete("{id}")]
public ActionResult<string> DeleteStudent(int id)
{
//删除id学生
if (id == 1)
{
return Ok("删除成功");
}
else
{
return NotFound("未找到!");
}
}
[HttpDelete("{id}")]
public ActionResult DeleteStudent(int id) //返回ActionResult
{
//删除id学生
if (id == 1)
{
return Ok("删除成功");
}
else
{
return NotFound("未找到!");
}
}
最后我们在总结下关于API参数获取的方式,在 [HttpGet("{id}")]
中我们看到有{id}
,这个就是占位符,从RUL中获取,不光可以配置占位符还可以配置路径的其它值,甚至可以随意组织,只要我们的参数明和占位符相同就行。
[HttpDelete("number/{id}/Name/{name}")] //自己组织的URL
public ActionResult<string> DeleteStudent(int id,string name)
{
//删除id学生
if (id == 1)
{
return Ok("删除成功");
}
else
{
return NotFound("未找到!");
}
}
当然也可以使用[FromRoute]
从route获取,另外我们还有一些Attribute用于从不同的地方获取参数,比如从QueryString获取。那么我的请求URL就应该是/RestCut/DeleteStudent?id=1
[HttpDelete]
public ActionResult<string> DeleteStudent([FromQuery] int id)
{
//删除id学生
if (id == 1)
{
return Ok("删除成功");
}
else
{
return NotFound("未找到!");
}
}
最后还有[FromHeader]
、[FromForm]
、[FromBody]
这些获取参数的方式,不清楚的使用的时候查询就行了。