准备工作:在此之前你需要了解关于.NET .Core的基础,前面几篇文章已经介绍:https://www.cnblogs.com/hcyesdo/p/12834345.html
首先需要明确一点的就是REST Api它不是一个标准,而是一种架构风格
什么是WebApi?
WebApi通常是指“使用HTTP协议并通过网络调用的API”,由于它使用了HTTP协议,所以需要通过URI信息来指定端点。
WebApi就是一个Web系统,通过访问URI可以与其进行信息交互。
而常用的MVC模式是主要用来构建UI的架构模式。
特点:松耦合,关注点分离、MVC不是一个完整的应用程序框架
MVC映射为API呢?
Model:它赋值处理程序数据的逻辑
View:它是程序里复制展示数据的那部分。构建API的时候,VView就是数据或资源的展示。通常使用JSON格式。
Controller,它复负责View和Model之间的交互。
需要注意的是,在配置服务的时候在core3.0以前可能写的是AddMvc,但是这个服务涉及了View视图以及TagHelper的一些功能,所以在做WebApi的时候用不到
public void ConfigureServices(IServiceCollection services) { //services.AddMvc(); core 3.0以前是这样写的,这个服务包括了 TageHelper等 WebApi不需要的东西,所有3.0以后可以不这样写 services.AddControllers(); }
注意配置中间件的区域管道顺序不能随意改动。
管道就是客户端通过一些指令指向服务器端,在这个过程中呢,会经过一些手动配置的中间件,比如说路由中间件、静态资源中间件等,从客户端出发到服务器端,将数据处理后,再由服务器端原路返回到客户端这样的一个过程。但是在请求的过程中也不排除中间件出现短路的情况,这样也就不会进入到第二个中间件了,而是直接返回到客户端。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
API对外合约:API消费者需要使用到三个概念
- 资源的标识(URI)
- HTTP方法(GET、POST)
- 有效载荷
API对外提供统一资源接口,业界对RESTful资源命名也有规则
关于RESTful API约束
使用名词而不是动词
需求:“我想获得系统里的所有用户”
常见错误:api/getusers
分析:这里的“获取”就是一个动词,而我们的目的应该是“用户”,即用户是一个名词
正确做法:GET api/user
要体现资源的结构/关系
通过id获取单个用户应该是:api/user/{userId},而不是 api/user/users。这样写就是让API具有很好的可读性和可预测性
需求案例1:
系统存在两个资源:Company(公司)、Employee(员工),现在需要获取某个公司下的所有员工
分析:应该使用HTTP GET。API在设计的时候需要体现公司与员工的一个包含关系
常见错误做法:api/employees,api/employee/{companyId} 。这两个URI都没有体现公司和员工的一个包含关系
建议做法:api/companies/{companyId}/employees
需求案例2:
需要获取某个公司下的某个员工
常见错误做法:api/employees/{employeeId}
建议做法:api/companies/{companyId}/employees/{employeeId}
自定义查询怎么命名?
需求:获取所有用户信息,并且按年龄从大到小排序
常见错误做法:api/user/orderby/age
建议做法:api/user?orderby=age (通过QueryString查询字符串,多条件使用 & 符号)
HTTP状态码
请求是否成功?如果请求失败了,谁来为此负责
2xx 开头状态码
200 - OK,表示请求成功
201 - Created,表示请求成功并创建了资源
204 - No Content,请求成功,但是不应该返回任何对象,例如删除操作
3xx 开头状态码
用于跳转。例如告诉浏览器搜索引擎,某个页面的网址已经永久改变,绝大多数的WebApi都不需要这类的状态码
4xx 开头:客户端错误
400 - Bad Request,表示API消费者发送到服务器的请求是有错误的
401 - Unauthorized,表示没有提供授权信息或者提供的授权信息有误
403 - Forbidden,表示身份认证已经通过,但是已认证的用户却无法访问请求的资源
404 - NotFound,表示请求的资源不存在
405 - Method not allowed,当尝试发送请求到资源的时候,使用了不被支持的HTTP方法
406 - Not acceptable,表示API消费者请求的表述格式并不被WebApi所支持,并且API不会提供默认的表述格式
5xx 开头状态码
500 - Internal serever error,表示服务器出现了错误,客户端无能为力,只能以后再试试
还有就是RESTful API 返回的结果不一定Json格式的
关于如何标注路由属性 uri ?
先看控制器代码:
using Microsoft.AspNetCore.Mvc; using Routine.Api.Service; using System; using System.Threading.Tasks; namespace Routine.Api.Controllers { [ApiController] //好处:ApiController不是强制的 //1.会启用使用属性路由(Attribute Routing) //2.自动HTTP 400响应 //3.推断参数的绑定源 //4.Multipart/form-data 请求推断 //5.错误状态代码的问题详细信息 [Route("api/companies")] //写法一 //[Route("api/[controller]")] //写法二:意思是相当于刨除了Controller后缀,获取前面的 Companies C可以是小写,如果你改名了那么你路由的uri也跟着变了(不建议这样写) public class CompaniesController:ControllerBase { private readonly ICompanyRepository _companyRepository; public CompaniesController(ICompanyRepository companyRepository) { _companyRepository = companyRepository ?? throw new ArgumentNullException(nameof(companyRepository)); } [HttpGet] //IActionResult定义了一些合约,它可以代表ActionResult返回的结果 public async Task<IActionResult> GetCompanies() { var companies =await _companyRepository.GetCompaniesAsync();//读取出来的是List return Ok(companies); } [HttpGet("{companyId}")] // Controller标注了ApiController => uri=> api/companies/{companyId} public async Task<IActionResult> GetCompany(Guid companyId) { //判断该公司是否存在方法一:这种方法在处理并发请求时可能会出现错误,原因是查到之后,进行删除,进入company后也可能是404找不到了 //var exists =await _companyRepository.CompanyExistsAsync(compamyId); //if (!exists) //{ // //不存在应该返回404 // return NotFound(); //} var company = await _companyRepository.GetCompanyAsync(companyId);//读取出来的是List //方法二 if (company==null) { return NotFound(); } return Ok(company); } } }
为了更好的构建RESTful API 对于 uri 的设计规则也有很严格的要求。
在控制器标注 ApiController,它会自动启用路由属性
通过 [Route] 设计路由规则
比如:接口一:GetCompanies,请求的方式:GET,通过Route 去设置路由规则 [Router("api/companies")],即查询所有公司信息
[Router("api/companies")] => api/companies
接口二:GetCompany,请求方式:GET,只不过在 添加了 [HTTPGET("{companyId}")] =>api/companies/{companyId},即查询某一公司的信息
关于第二种路由写法请看注释
通过Postman工具测试一下
测试一:接口一
测试二:接口二
以上两个接口测试完毕!!!
对于ASP.NET Core 3.x以前对于 404 NotFound请求状态码输出的格式不太友好,而ASP.NET Core 3.x对于404请求状态码也做了友好的提示。
现在将接口伪造错误信息,提示 404 如图:
关于构建 RESTful API 存在的内容协商:
所谓内容协商就是这样一个过程,针对一个响应,当有多种表述格式可用时,选取最佳的一种表述格式,这些表述可以是XML,JSON,甚至是自定义的格式规则
Accept Header:负责指定输出类型
Media Type(媒体类型)
- application/json
- application/xml
404 Not Acceptable
输出格式:ASP.NET Core 里面对应的就是 Output Formatters 我们称为:输出类型的格式化器
也就是说如果一个API消费者,设置了Accept Header的媒体类型为Json,那么这个RESTful API也应该返回的是JSON,
但是呢如果服务器只接收XML的格式,这个时候请求的媒体类型不被服务器所接受,那么就会返回 406 这个状态码
总而言之,尽量避免不写Accept Header,避免客户端和服务器端接收和返回的类型不一致导致错误。
有输出那么就会有输入了!!!
Content-Type-Header:负责指定输入
Media Type(媒体类型)
- application/json
- application/xml
输出格式:ASP.NET Core里面对应的就是 Input Formatters
比如说:对于一个客户端的POST请求,即添加资源信息,那么就需要输入参数,这些参数可能是放在Body里面,那么在Body里面的这些参数可能是对象的那种格式。那么我们就需要通过 Content-Type-Header来确定Body里面的参数是什么样的类型,可能是Json也可能是Xml或者是自定义的格式,指明之后,RESTful API才能更好的对这些参数进行处理。
看Startup类代码:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Routine.Api.Data; using Routine.Api.Service; namespace Routine.Api { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { //services.AddMvc(); core 3.0以前是这样写的,这个服务包括了TageHelper等 WebApi不需要的东西,所有3.0以后可以不这样写 services.AddControllers(setup => { //setup.ReturnHttpNotAcceptable=false;//如果客户端默认为xml格式,服务器端为json,false就不会返回406 setup.ReturnHttpNotAcceptable = true;//如果请求的类型和服务器请求的类型不一致就返回406 //setup.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); //setup.OutputFormatters.Insert(0, new XmlDataContractSerializerOutputFormatter()); }).AddXmlDataContractSerializerFormatters(); //配置接口服务:涉及到这个服务注册的生命周期这里采用AddScoped,表示每次的Http请求 services.AddScoped<ICompanyRepository, CompanyRepository>(); //获取配置文件中的数据库字符串连接 var sqlConnection = Configuration.GetConnectionString("SqlServerConnection"); //配置上下文类DbContext,因为它本身也是一套服务 services.AddDbContext<RoutineDbContext>(options => { options.UseSqlServer(sqlConnection); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } }
setup.ReturnHttpNotAcceptable就是处理是在客户端与服务器端数据产生冲突时,是否要即将产生 406 的状态码。
- true:产生
- false:不产生
setup.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter())
分析:实际上OutputFormatters 是一个集合 ,通过Add方法添加服务器允许接受XML格式的数据功能。因为集合中默认只有Json
setup.OutputFormatters.Insert(0,new XmlDataContractSerializerOutputFormatter())
分析:实际上刚刚写的是一种方法。Insert就是指明格式顺序,默认是JSON,通过Insert设置 0 ,就是指明XML为默认接受的数据格式
实际上以上两种写法都是 ASP.NET Core 3.x以前的写法。
ASP.NET Core 3.x的实际写法:就是在AddControllers后面添加XmlDataContractSerializerOutputFormatter方法。这样不管是输入输出都已经设置好了XML的格式数据
postman接口测试:取消setup.OutputFormatters.Insert(0,new XmlDataContractSerializerOutputFormatter())的注释
默认xml:
最后,关于构建RESTFUL Api的URI规则及原理
动作 | HTTP方法 | 请求的参数(Payload) | 参数位置 | URI | 请求前 | 请求后 | 响应内容 |
查询 | GET | 查询参数 | 可含查询字符串(QueryString) |
/api/companies/{companyId} /api/companies |
无修改 |
单个资源 多个资源的集合 |
|
创建/添加 | POST | 要创建的单个资源信息 | Body | /api/companies | 旧 | 新 | 新创建的单个资源 |
局部修改/更新 | PATCH |
待修改的资源 JsonPatchDocument |
Body | /api/companies/{companyId} |
a:1 b:2 |
a:1 b:3 |
无须返回 |
替换 | PUT | 要替换的单个资源信息 | Body | /api/companies/{companyId} |
a:1 b:2 |
a:2 b:3 |
无须返回 |
使用预定义的表示进行创建 | PUT | 要创建的单个资源信息 | Body | /api/companies/{companyId} | 旧 | 新 | 返回新创建的资源 |
移除/删除 | DELETE | 无 | 可含查询字符串(QueryString) | /api/companies/{companyId} |
a b |
a | 无须返回 |
.NET Core 3.x构建RESTful API 待续!!!