ABP 适用性改造 - 添加 API 版本化支持
Overview
在前面的文章里有针对 abp 的项目模板进行简化,构建了一个精简的项目模板,在使用过程中,因为我们暴露的 api 需要包含版本信息,我们采取的方式是将 api 的版本号包含在资源的 URI 中。因为 abp 默认的 api 是没有版本的概念的,所以这里为了实现 api 版本化需要针对 abp 项目的 api 路由进行改造,从而满足我们的需求。本篇文章则是实现这一改造过程的演示说明,希望可以对你有所帮助
完整的项目模板如下所示
模板源码地址:https://github.com/danvic712/ingos-abp-api-template
Step by Step
在 abp 项目中,可以通过如下的两种方式实现 api 接口的定义
- 传统的 web api 实现方式,通过定义 controller 来完成资源 api 构建
- 通过 abp 框架内置的 Auto API Controller 功能,将项目中定义的应用服务(application service),自动暴露成 api 接口
因为这里的两种方式在项目开发中我们都会使用到,所以这里需要针对这两种不同的方式都实现 api 版本化的支持
对于第一种方式的 api 版本化支持,我在之前的文章中有提到过,如果你有需要的话,可以点击此处进行查阅,这里就不再赘述了,本篇文章主要关注点在如何对 abp 自动生成的 api 接口进行改造,实现将 api 版本信息添加到路由中
因为这里我使用的是精简后的 abp 模板,与默认的 abp 项目中的程序集名称存在差异,程序集之间的对应关系如下所示,你可以对照默认的项目进行修改
- xxx.API => xxx.HttpApi.Host
- xxx.Application => xxx.Application
2.1、添加程序集
对于 api 版本化的实现,这里也是基于下面的两个类库来的,因此,在使用之前我们需要先在项目中通过 nuget 添加对于这两个程序集的引用
## 添加 API 多版本支持
Install-Package Microsoft.AspNetCore.Mvc.Versioning
## 添加 Swagger 文档的 API 版本显示支持
Install-Package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
因为在 xxx.API 这个项目中已经使用到的 abp 的程序集中已经间接引用了 *.Versioning 这个程序集,所以这里就可以选择不添加,只需要将 *.Versioning.ApiExplorer 添加引用到项目即可
对于 xxx.Application 这个类库,因为不会关联到 Swagger 的相关设置,所以这里只需要在项目中添加 *.Versioning 的引用
2.2、路由改造
当所需的程序集引用添加完成之后,就可以针对 abp 生成的路由格式进行改造,从而实现我们想要添加 api 版本信息到路由地址中的目的
对于通过创建 controller 来暴露 api 服务的接口,我们可以直接在 controller or action 上添加 ApiVersion 特性,然后修改特性路由即可,示例代码如下所示
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class VaulesController : ControllerBase
{
// action ...
}
而对于 abp 基于 application service 自动生成的 api,在默认的项目模板中,你可以在 *HttpApiHostModule 类中找到如下的配置,最终可以生成下图中的 api 路由格式
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
ConfigureConventionalControllers(context);
}
private void ConfigureConventionalControllers()
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(XXXApplicationModule).Assembly);
});
}

从 abp 的文档中可知,基于约定俗成的定义,所有根据 application service 自动生成的 api 全部会以 /api 开头,而路由路径中的的 */app/* 我们则可以通过修改 RootPath 变量值的方式进行调整,例如,你可以将 app 修改成 your-api-path-define
private void ConfigureConventionalControllers()
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(XXXApplicationModule).Assembly, opts =>
{
opts.RootPath = "your-api-path-define";
});
});
}
这里调整之后的 api 路由就会变成 /api/your-api-path-define/*,因此这里我们就可以通过修改变量值的方式来实现路由中包含 api 的版本信息,eg. /api/v1/*
找到能够调整的地方后,我们就需要思考具体的改造方式了,如果这里我们写死变量值为 v1 or v2 的话,意味着整个 XXXApplicationModule 程序集中的 application service 生成的 api 版本就限制死了,后续的可扩展性就太差了,所以这里需要实现一个动态的配置
因此这里同样是借助了上面引用的组件包,选择通过添加 ApiVersion 特性的方式来标明应用服务所映射的 api 版本信息,例如下面对应生成的 api 版本为 1.0
[ApiVersion("1.0")]
public class BookAppService :
CrudAppService<
Book, // The Book entity
BookDto, // Used to show books
Guid, // Primary key of the book entity
PagedAndSortedResultRequestDto, // Used for paging/sorting
CreateUpdateBookDto>, // Used to create/update a book
IBookAppService // implement the IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
}
定义了服务对应的 api 版本之后,这里就可以通过路由模板变量值的方式来替换 RootPath 参数值,因为这里的路由相对于原来的方式来说是一种不确定的,所以这里我们将配置路由的方法放在 abp 的 PreConfigureServices 生命周期函数中,位于该函数中的代码会在整个项目所有模块的 ConfigureServices 方法执行之前执行,调整后的代码如下
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpAspNetCoreMvcOptions>(options =>
{
// 依据 api 版本信息动态设置路由信息
options.ConventionalControllers.Create(typeof(IngosAbpTemplateApplicationModule).Assembly,
opts => { opts.RootPath = "v{version:apiVersion}"; });
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
ConfigureConventionalControllers(context);
}
private void ConfigureConventionalControllers(ServiceConfigurationContext context)
{
// 基于 PreConfigureServices 中的配置进行
Configure<AbpAspNetCoreMvcOptions>(options => { context.Services.ExecutePreConfiguredActions(options); });
}
当然,这里只是针对我们自己编写的应用服务进行的版本设定,对于 abp 框架所包含的一些 api 接口,可以直接在 PreConfigureServices 函数中通过直接指定 api 版本的方式来实现,例如这里我将权限相关的 api 接口版本设置为 1.0
PS,这里针对框架内置 api 的版本设定,并不会改变接口的路由地址,仅仅是为了下面将要实现的 swagger 依据 api 版本号进行分组显示时可以将内置的 api 暴露出来
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpAspNetCoreMvcOptions>(options =>
{
// 依据 api 版本信息动态设置路由信息
options.ConventionalControllers.Create(typeof(IngosAbpTemplateApplicationModule).Assembly,
opts => { opts.RootPath = "v{version:apiVersion}"; });
// 指定内置权限相关 api 版本为 1.0
options.ConventionalControllers.Create(typeof(AbpPermissionManagementHttpApiModule).Assembly,
opts => { opts.ApiVersions.Add(new ApiVersion(1, 0)); });
});
}
配置好路由之后,就可以将 api 版本服务以及给到 swagger 使用的 api explorer 服务注入到 IServiceCollection 中,这里的配置项和之前的方式一样就不做解释了,完善后的方法代码如下所示
private void ConfigureConventionalControllers(ServiceConfigurationContext context)
{
Configure<AbpAspNetCoreMvcOptions>(options => { context.Services.ExecutePreConfiguredActions(options); });
context.Services.AddAbpApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ApiVersionReader = new UrlSegmentApiVersionReader();
var mvcOptions = context.Services.ExecutePreConfiguredActions<AbpAspNetCoreMvcOptions>();
options.ConfigureAbp(mvcOptions);
});
context.Services.AddVersionedApiExplorer(option =>
{
option.GroupNameFormat = "'v'VVV";
option.AssumeDefaultVersionWhenUnspecified = true;
});
}
2.3、Swagger 改造
因为改造前的项目是不存在 api 版本的概念的,所以默认的 swagger 是会显示出所有的接口,而当项目可以支持 api 版本化之后,这里就应该基于 api 版本生成不同的 json 文件,达到 swagger 可以基于 api 的版本来分组显示的目的
因为在上面的代码中已经将 api explorer 服务注入到了 IServiceCollection 中,所以这里可以直接使用 IApiVersionDescriptionProvider 获取到 api 的版本信息,从而据此生成不同的 swagger json 文件,swagger 相关的配置代码如下
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
ConfigureSwaggerServices(context);
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseSwagger();
app.UseAbpSwaggerUI(options =>
{
options.DocumentTitle = "IngosAbpTemplate API";
// 默认显示最新版本的 api
//
var provider = context.ServiceProvider.GetRequiredService<IApiVersionDescriptionProvider>();
var apiVersionList = provider.ApiVersionDescriptions
.Select(i => $"v{i.ApiVersion.MajorVersion}")
.Distinct().Reverse();
foreach (var apiVersion in apiVersionList)
options.SwaggerEndpoint($"/swagger/{apiVersion}/swagger.json",
$"IngosAbpTemplate API {apiVersion?.ToUpperInvariant()}");
});
}
private static void ConfigureSwaggerServices(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAbpSwaggerGenWithOAuth(
configuration["AuthServer:Authority"],
options =>
{
// 获取 api 版本信息
var provider = context.Services.BuildServiceProvider()
.GetRequiredService<IApiVersionDescriptionProvider>();
// 基于大版本生成 swagger
foreach (var description in provider.ApiVersionDescriptions)
options.SwaggerDoc(description.GroupName, new OpenApiInfo
{
Contact = new OpenApiContact
{
Name = "Danvic Wang",
Email = "danvic.wang@outlook.com",
Url = new Uri("https://yuiter.com")
},
Description = "IngosAbpTemplate API",
Title = "IngosAbpTemplate API",
Version = $"v{description.ApiVersion.MajorVersion}"
});
options.DocInclusionPredicate((docName, description) =>
{
// 获取主要版本,如果不是该版本的 api 就不显示
var apiVersion = $"v{description.GetApiVersion().MajorVersion}";
if (!docName.Equals(apiVersion))
return false;
// 替换路由参数
var values = description.RelativePath
.Split('/')
.Select(v => v.Replace("v{version}", apiVersion));
description.RelativePath = string.Join("/", values);
return true;
});
// 取消 API 文档需要输入版本信息
options.OperationFilter<RemoveVersionFromParameter>();
});
}
自此,整个关于 api 版本化的调整就已经完成了,完整的代码可以点击此处跳转到 github 上进行查看,最终实现效果如下所示

ABP 适用性改造 - 添加 API 版本化支持的更多相关文章
- ABP 适用性改造 - 精简 ABP CLI 生成的项目结构
Overview 不管是公司或者个人都会有不同的开发习惯,通过建立项目模板,既可以使开发人员聚焦于业务功能的开发,也可以在一定程度上统一不同开发人员之间的开发风格.在使用 ABP 框架的过程中,对于 ...
- 如何版本化你的API?--转
原文地址:http://www.infoq.com/cn/news/2017/09/How-versioning-API 如何版本化API需要考虑各种实际业务场景,但是一个完备的API应该是: 和客户 ...
- IPFS - 可快速索引的版本化的点对点文件系统(草稿3)
摘要 星际文件系统是一种点对点的分布式文件系统, 旨在连接所有有相同的文件系统的计算机设备.在某些方面, IPFS类似于web, 但web 是中心化的,而IPFS是一个单一的Bittorrent 群集 ...
- ArcGIS分支版本化( Branch Versioning )技术介绍
概述 分支版本化技术是有别于传统的SDE版本化技术,它用于支持WebGIS模式下的多用户长事务编辑. 优势功能 使用分支版本化技术将获得以下功能 1. 支持长事务的编辑. 2. 支持Undo和Redo ...
- ABP框架 - 动态Web Api层
文档目录 本节内容: 创建动态Web Api控制器 ForAll 方法 重写 ForAll ForMethods Http 动词 WithVerb 方法 HTTP 特性 命名约定 Api 浏览器 Re ...
- 第12章 添加对外部认证的支持 - Identity Server 4 中文文档(v1.0.0)
注意 对于任何先决条件(例如模板),首先要查看概述. 接下来,我们将添加对外部认证的支持.这非常简单,因为您真正需要的是ASP.NET Core兼容的身份验证处理程序. ASP.NET Core本身支 ...
- 【ZZ】Visual C++ 6.0 精简安装版(支持VA、ICC 等等安装)
(2012-04-22 08:10:10) 标签: it 分类: 软件_Software Visual C++ 6.0 精简安装版(支持VA.ICC 等等安装) 2012-04-16 21:07 想找 ...
- 1个多商户、多平台版 微信小程序(多商户、多平台版),影城行业、影业连锁 多商户、多平台版微信小程序。(基于多平台版,支持在业务上 可给 每个单独影城 分发定制单独的小程序版本)
1个 影城行业 微信小程序(多商户.多平台版), 影业连锁 多商户.多平台版微信小程序.(基于多平台版,支持在业务上 可给 每个单独影城 分发定制单独的小程序版本) 资讯QQ: 876635409 ...
- IdentityServer4 中文文档 -13- (快速入门)切换到混合流并添加 API 访问
IdentityServer4 中文文档 -13- (快速入门)切换到混合流并添加 API 访问 原文:http://docs.identityserver.io/en/release/quickst ...
随机推荐
- 大送福利!市场香饽饽VAST到底什么来头,为何被高价估值
近日,NGK星空计划新币VAST成为了香饽饽,还未正式上线前,市场讨论的热度就居高不下.如今NGK推出1万VAST免费送新人福利更是将这波热度推向了高潮. 具体福利规则:在美国加州时间2021年2月8 ...
- 经典面试题:在浏览器地址栏输入一个 URL 后回车,背后发生了什么
尽人事,听天命.博主东南大学硕士在读,热爱健身和篮球,乐于分享技术相关的所见所得,关注公众号 @ 飞天小牛肉,第一时间获取文章更新,成长的路上我们一起进步 本文已收录于 CS-Wiki(Gitee 官 ...
- Linux安装ElasticSearch7.X & IK分词器
前言 安装ES之前,请先检查JDK版本,es使用java编写,强依赖java环境.JDK安装过程略. 安装步骤 1.下载地址 点击这里下载7.2.0 2.解压elasticsearch-7.2.0-l ...
- Linux 查看磁盘是否为SSD
第一步,找到磁盘 ll /dev/sd* ll /dev/vd* 第二步,查对应磁盘类型 cat /sys/block/sda/queue/rotational 结果: 返回0:SSD盘 返回1: ...
- Oracle数据库配置监听程序
最近在学习Oracle数据库,从安装到配置监听程序基本靠百度... 不得不说百度真的很nice!!! 下面是我的Oracle服务端(PL/SQL Developer)出现的监听程序的问题及我解决的方法 ...
- Java基本概念:异常
一.简介 描述: 异常(Exception)指不期而至的各种状况,异常发生的原因有很多,通常包含以下几大类: 用户输入了非法数据. 要打开的文件不存在. 网络通信时连接中断,或者JVM内存溢出. 异常 ...
- wxWidgets源码分析(3) - 消息映射表
目录 消息映射表 静态消息映射表 静态消息映射表处理过程 动态消息映射表 动态消息映射表处理过程 消息映射表 消息是GUI程序的核心,所有的操作行为均通过消息传递. 静态消息映射表 使用静态Event ...
- 后端程序员之路 35、Index搜索引擎实现分析4-最终的正排索引与倒排索引
# index_box 提供搜索功能的实现- 持有std::vector<ITEM> _buffer; 存储所有文章信息- 持有ForwardIndex _forward_index; ...
- 区分函数防抖&函数节流
1. 概念区分 函数防抖:触发事件后,在n秒内函数只能执行一次,如果触发事件后在n秒内又触发了事件,则会重新计算函数延执行时间. 简单说: 频繁触发, 但只在特定的时间内才执行一次代码,如果特定时间内 ...
- Hi3559AV100外接UVC/MJPEG相机实时采图设计(一):Linux USB摄像头驱动分析
下面将给出Hi3559AV100外接UVC/MJPEG相机实时采图设计的整体流程,主要实现是通过V4L2接口将UVC/MJPEG相机采集的数据送入至MPP平台,经过VDEC.VPSS.VO最后通过HD ...