ASP.NET Core 实战:构建带有版本控制的 API 接口
一、前言
在上一篇的文章中,主要是搭建了我们的开发环境,同时创建了我们的项目模板框架。在整个前后端分离的项目中,后端的 api 接口至关重要,它是前端与后端之间进行沟通的媒介,如何构建一个 “好用” 的 api 接口,是需要我们后端人员好好思考的。
在系统迭代的整个过程中,不可避免的会添加新的资源,或是修改现有的资源,后端接口作为暴露给外界的服务,变动的越小,对服务的使用方造成的印象就越小,因此,如何对我们的 api 接口进行合适的版本控制,我们势必需要首先考虑。
系列目录地址:asp.net core 项目实战
仓储地址:https://github.com/lanesra712/grapefruit.vucore
二、step by step
项目总是在不断迭代的,某些时候,因为业务发展的需要,需要将现有的接口进行升级,而原有的接口却不能立刻停止使用。比如说,你开发了一个接口提供给爱啪啪 1.0 版本使用,后来爱啪啪的版本迭代了,需要接口返回的数据与原先 1.0 版本返回的数据不同了,这时候,接口肯定是需要升级的,可是如果直接升级原有的接口,还在使用 1.0 版本的用户不就 gg 了,因此,如何做到既可以让 1.0 版本的用户使用,也可以让 2.0 版本的用户使用就需要好好考虑了,常见的解决方案,主要有以下几种。
a)使用不同的 api 名称
最简单粗暴,需要变更接口逻辑时就重新起个 api 名称,新的版本调用新的 api 名称,旧的版本调用旧的 api 名称。
https://yuiter.com/api/secret/login ##爱啪啪 1.0 https://yuiter.com/api/secret/newlogin ##爱啪啪 2.0
b)在 url 中标明版本号
直接将 api 版本信息添加到请求的 url 中,调用不同版本的 api ,就在 url 中直接标明使用的是哪个版本。
https://yuiter.com/api/v1/secret/login ##爱啪啪 1.0 https://yuiter.com/api/v2/secret/login ##爱啪啪 2.0
c)请求参数中添加版本信息
将 api 的版本信息作为请求的一个参数传递,通过指定参数值来确定请求的 api 版本。
https://yuiter.com/api/secret/login?version=1 ##爱啪啪 1.0 https://yuiter.com/api/secret/login?version=2 ##爱啪啪 2.0
d)在 header 中标明版本号
前端在请求 api 接口时,在 header 中添加一个参数用来表明请求的版本信息,后端通过前端在 header 中设置的参数来判断,从而执行不同的业务逻辑分支。
post https://yuiter.com/api/secret/login host: yuiter.com api-version: v1 ##爱啪啪 1.0 post https://yuiter.com/api/secret/login host: yuiter.com api-version: v2 ##爱啪啪 2.0
在 grapefruit.vucore 这个项目中,我选择将 api 的版本信息添加到请求的地址中,从而明确的指出当前请求的接口版本信息。
1、swagger 集成
后端完成了接口之后,肯定需要告诉前端,不管是整理成 txt/excel/markdown 文档,亦或是写完一个接口就直接发微信告诉前端,总是要多做一步的事情,而 swagger 则可以帮我们省去这一步。通过配置之后,swagger 就可以根据我们的接口自动生成 api 的接口文档,省时,省力。当然,如果前端小姐姐单身可撩,而你碰巧有意的话,另谈。
swagger 是一个可以将接口文档自动生成,同时可以对接口功能进行测试的开源框架,在 asp.net core 环境下,主流的有 swashbuckle.aspnetcore 和 nswag 这两个开源框架帮助我们生成 swagger documents。这里,我采用的是 swashbuckle.aspnetcore。
在使用 swashbuckle.aspnetcore 之前,首先我们需要在 api(grapefruit.webapi) 项目中添加对于 swashbuckle.aspnetcore 的引用。你可以直接右键选中 api 项目选择管理 nuget 程序包进行加载引用,也可以通过程序包管理控制台进行添加引用,这里注意,使用程序包管理控制台时,你需要将默认的项目修改成 api(grapefruit.webapi) 项目。当引用添加完成后,我们就可以在项目中配置 swagger 了。
install-package swashbuckle.aspnetcore
asp.net core 的本质上可以看成是一个控制台程序,在我们创建好的 asp.net core web api 项目中,存在着两个类文件:program.cs 以及 startup.cs。与控制台应用一样,program 类中的 main 方法是整个程序的入口,在这个方法中,我们将配置好的 iwebhostbuilder 对象,构建成 iwebhost 对象,并运行该 iwebhost 对象从而达到运行 web 项目的作用。
在框架生成的 program 类文件中,在配置 iwebhostbuilder 的过程时,框架默认为我们添加了一些服务,当然,这里你可以注释掉默认的写法,去自己创建一个 webhostbuilder 对象。同时,对于一个 asp.net core 程序来说,startup 类是必须的(你可以删除生成的 startup 类,重新创建一个新的类,但是,这个新创建的类必须包含 configure 方法,之后只需要在 usestartup<startup> 中将该类配置为 startup 类即可),这里如果不指定 startup 类会导致启动失败。
在 startup 类中,存在着 configureservices 和 configure 这两个方法,在 configureservices 方法中,我们将自定义服务通过依赖注入的方式添加到 iservicecollection 容器中,而这些容器中的服务,最终都可以在 configure 方法中进行使用;而 configure 方法则用于指定 asp.net core 应用程序将如何响应每一个 http 请求,我们可以在这里将我们自己创建的中间件(middleware)绑定到 iapplicationbuilder 上,从而添加到 http 请求管道中。
这里只是很粗略的说明了 asp.net core 项目的启动过程,想要仔细了解启动过程的推荐园子里的这篇文章 =》asp.net core 2.0 : 七.一张图看透启动背后的秘密,因为 asp.net core 2.1 版本相比于 2.0 版本又有些改变,这里有一些不一样的地方需要你去注意。
当我们简单了解了启动过程后,就可以配置我们的 swagger 了。swashbuckle.aspnetcore 帮我们构建好了使用 swagger 的中间件,我们只需要直接使用即可。
首先我们需要在 configureservices 方法中,将我们的服务添加到 iservicecollection 容器中,这里,我们需要为生成的 swagger document 进行一些配置。
services.addswaggergen(s => { s.swaggerdoc("v1", new info { contact = new contact { name = "danvic wang", email = "danvic96@hotmail.com", url = "https://yuiter.com" }, description = "a front-background project build by asp.net core 2.1 and vue", title = "grapefruit.vucore", version = "v1" }); });
之后,我们就可以在 configure 方法中启用我们的 swagger 中间件。
app.useswagger(); app.useswaggerui(s => { s.swaggerendpoint("/swagger/v1/swagger.json", "grapefruit.vucore api v1.0"); });
此时,当你运行程序,在域名后面输入/swagger 即可访问到我们的 api 文档页面。因为项目启动时默认访问的是我们 api/values 的 get 请求接口,这里我们可以打开 properties 下的 launchsetting.json 文件去配置我们的程序默认打开页面。
从上面的图可以看出,不管是使用 iis 或是程序自托管,我们默认打开的 url 都是 api/values,这里我们将两种启动方式的 launchurl 值都修改成 swagger 之后再次运行我们的项目,可以发现,程序默认的打开页面就会变成我们的 api 文档页面。
我们使用 api 文档的目的,就是为了让前端知道请求的方法地址是什么,需要传递什么参数,而现在,并没有办法显示出我们对于参数以及方法的注释,通过查看 swashbuckle.aspnetcore 的 github 首页可以看到,我们可以通过配置,将生成的 json 文件中包含我们对于 controller or action 的 xml 注释内容,从而达到显示注释信息的功能(最终呈现的 swagger doc 是根据之前我们定义的这个 “/swagger/v1/swagger.json” json 文件来生成的)。
右键我们的 api 项目,属性 =》生产,勾选上 xml 文档文件,系统会默认帮我们创建生成 xml 文件的地址,这时候,我们重新生成项目,则会发现,当前项目下会多出这个 xml 文件。在重新生成项目的过程中,你会发现,错误列表会显示很多警告信息,提示我们一些方法没有添加 xml 注释。如果你和我一样强迫症的话,可以把 1591 这个错误添加到上面的禁止显示警告中,这样就可以不再显示这个警告了。
创建好 xml 的注释文件后,我们就可以配置我们的 swagger 文档,从而达到显示注释的功能。这里,因为我会在 grapefruit.application 类库中创建各种的 dto 对象,而接口中是会调用到这些 dto 对象的。因此,为了显示这些 dto 上的注释信息,这里我们也需要生成 grapefruit.application 项目的 xml 注释文件。
ps:这里我是将每个项目生成的注释信息 xml 文档地址都放在了程序的基础路径下,如果你将 xml 文档生成在别的位置,这里获取 xml 的方法就需要你进行修改。
services.addswaggergen(s => { //... //add comments description // var basepath = path.getdirectoryname(appcontext.basedirectory);//get application located directory var apipath = path.combine(basepath, "grapefruit.webapi.xml"); var dtopath = path.combine(basepath, "grapefruit.application.xml"); s.includexmlcomments(apipath, true); s.includexmlcomments(dtopath, true); });
当我们把 swagger 配置完成之后,我们就可以创建具有版本控制的 api 接口了。
2、带有版本控制的 api 接口实现
在请求的 api url 中标明版本号,我不知道你第一时间看到这个实现方式,会想到什么,对于我来说,直接在路由信息中添加版本号不就可以了。。。em,这样过于投机取巧了。。。。
[route("api/v1/[controller]")]//添加版本信息为v1 [apicontroller] public class valuescontroller : controllerbase { }
想了想,在 url 中添加版本号,这个版本号是不是很像我们在 mvc 中使用的 area。
area 是 mvc 中经常使用到的一个功能,我们通常会将某些小的模块拆分成一个个的 area,而这一个个的小 area 其实就是这个 mvc 项目中的 mvc。通过为 controller 和 action 添加另一个路由参数 area,从而达到创建具有层次路由的结构。比如,这里,我们可以创建一个 area 叫 v1,用来存储我们 1.x 版本的 api 接口,之后如果有新的 api 版本,新增一个 area 即可,是不是很简单,嗯,说干就干。
右击我们的 api 项目,选择添加区域,新增的 area 名称为 v1。
当 asp.net core 的脚手架程序添加完成 area 后,则会打开一个文件提示我们需要在 mvc 中间件中创建适用于 area 的路由定义。
app.usemvc(routes => { routes.maproute( name : "areas", template : "{area:exists}/{controller=home}/{action=index}/{id?}" ); });
当我们添加好路由规则定义后,我们在 area 的 controllers 文件夹下添加一个 webapi controller。不同于 asp.net 中的 area ,当我们在 asp.net core 创建好一个 area 之后,脚手架生成的文件中不再有 xxxarearegistration(xxx 为 area 的名称)文件去注册这个 area,而我们只需要在 area 中的 controller 中添加 area 特性,即可告诉系统框架,这个 controller 是在当前的 area 下的。
如果你有自己尝试的话,就会发现,当我们创建好一个 v1 的 area 后,这个请求的地址并没有按照我们的想法会体现在路由信息中,我们最后还是需要在 route 中手动指明 api 版本。
[area("v1")] [route("api/v1/[controller]")] [apicontroller] public class valuescontroller : controllerbase { }
这样的话,和最开始直接在路由信息中写死版本信息其实也就没什么差别了,上网搜了搜,发现巨硬爸爸,也早已为我们准备好了实现版本控制 api 的利器 - microsoft.aspnetcore.mvc.versioning。
和上面使用 swashbuckle.aspnetcore 的方式相同,在我们使用 versioning 之前,需要在我们的 api 项目中添加对于该 dll 的引用。这里需要注意下安装的版本问题,因为 grapefruit.vucore 这个框架距离现在搭建也有几个月的时间了,在这个月初的时候 .net core 2.2 也已经发布了,如果你和我一样还是采用的 .net core 2.1 版本的话,这里安装的 versioning 版本最高只能到 2.3。
install-package microsoft.aspnetcore.mvc.versioning
当我们安装完成之后,就可以进行配置了。
public void configureservices(iservicecollection services) { services.addapiversioning(o => { o.reportapiversions = true;//return versions in a response header o.defaultapiversion = new apiversion(1, 0);//default version select o.assumedefaultversionwhenunspecified = true;//if not specifying an api version,show the default version }); }
reportapiversions:这个配置是可选的,当我们设置为 true 时,api 会在响应的 header 中返回版本信息。
defaultapiversion:指定在请求中未指明版本时要使用的默认 api 版本。这将默认版本为1.0。
assumedefaultversionwhenunspecified:这个配置项将用于在没有指明 api 版本的情况下提供请求,默认情况下,会请求默认版本的 api,例如,这里就会请求 1.0 版本的 api。
这里,删除我们之前的创建的 area 和默认的 valuescontroller,在 controllers 文件夹下新增一个 v1 文件夹,将所有 v1 版本的 controller 都建在这个目录下。新建一个 controller,添加上 apiversion attribute 指明当前的版本信息。因为我采用的方案是在 url 中指明 api 版本,所以,我们还需要在 route 中修改我们的路由属性以对应 api 的版本。这里的 v 只是一个默认的惯例,你也可以不添加。
[apiversion("1.0")] [route("api/v{version:apiversion}/[controller]")] [apicontroller] public class vaulescontroller : controllerbase { }
当我们修改好我们的 controller 之后,运行我们的项目,你会发现,api 文档中显示的请求地址是不对的,难道是我们的配置没起作用吗?通过 swagger 自带的 api 测试工具测试下我们的接口,原来这里请求的 url 中已经包含了我们定义的版本信息,当我们指定错误的版本信息时,工具也会告诉我们这个版本的接口不存在。
虽然我们请求的 url 中已经带上了版本信息,但是 api 文档上显示的请求地址却是不准确的,强迫症,不能忍。这里,需要我们修改生成 swagger 文档的配置代码,将路由中的版本信息进行替换。重新运行我们的项目,可以发现,文档显示的 url 地址也已经正确了,自此,我们创建带有版本控制的 api 也就完成了。
public void configureservices(iservicecollection services) { services.addswaggergen(s => { //... //show the api version in url address s.docinclusionpredicate((version, apidescription) => { var values = apidescription.relativepath .split('/') .select(v => v.replace("v{version}", version)); apidescription.relativepath = string.join("/", values); return true; }); }); }
三、总结
本章使用了 microsoft.aspnetcore.mvc.versioning 这一组件来实现我们对于 api 版本控制的功能实现,可能你会有疑问,我们直接在路由中写明版本信息不是更简单吗?在我看来,使用这一组件的目的,在于我们可以以多种的方式实现 api 版本控制的目的,如果哪天你不想在 url 中指明版本信息后,你可以很快的使用别的形式来完成 api 的版本控制。另外,直接在路由中写上版本信息,是不是会显得我们比较 ‘low’,哈哈哈,开玩笑,最后祝大家圣诞快乐~~~
上一篇: 对python遍历文件夹中的所有jpg文件的实例详解
下一篇: javascript引用类型基础讲解