Asp-Net-Core开发笔记:API版本管理
前言
对于Web API应用程序而言,随着时间的推移以及需求的增加或改变,API必然会遇到升级的需求。事实上,Web API应用程序应该从创建时就考虑到API版本的问题。业务的调整、功能的增加、接口的移除与改名、接口参数变动、实体属性的添加、删除和更改等都会改变API的功能,从而带来版本的变更。
现有的资料大部分是使用 Microsoft.AspNetCore.Mvc.Versioning
这个包,但我实际使用的时候发现这个包早就不更新了,微软官方文档好像也没有这部分介绍,不过在这个包的nuget主页上有说已经换成新的 Asp.Versioning.Mvc
包,原来是微软改名部发力了,失敬失敬~ 😂
好在这个新的包在Github上有很详细的文档,但这改名速度实在是猛,为了实现这个功能,我走了不少弯路。😂
OK,本文基于 .Net6.0,以 AspNetCore WebApi
为例,介绍引入API版本管理的过程。
基础
指定版本的方法有两种,既可以使用[ApiVersion]特性,也可以使用版本约定方式。当定义了不同版本的API接口后,客户端可以通过如下多种方式来访问某一版本的API。
- 使用URL路径,如 api/v1.0/values
- 使用查询字符串,如 api/values?api-version=1.0
- 使用HTTP自定义消息头
- 使用媒体类型(Media Type)参数,如 Accept: application/json;v=2.0
ASP.NET Core MVC默认的方式是使用查询字符串,查询字符串使用的参数名为api-version。具体使用哪种方式由服务端指定(用下面介绍的 ApiVersionReader
属性来配置),既可以使用其中的一种,也可以同时使用多种不同的方式。
API版本的格式由主版本号与次版本号组成,此外还可以包含可选的两部分:版本组和状态。
[Version Group.]<Major>.<Minor>[-Status]
<Version Group>[<Major>[.Minor]][-Status]
版本组的格式为YYYY-MM-DD,它能够对API接口起到逻辑分组的作用,状态则能够标识当前版本的状况,如Alpha、Beta和RC等。以下是常见的版本格式:
- /api/foo?api-version=1.0
- /api/foo?api-version=2.0-Alpha
- /api/foo?api-version=2015-05-01.3.0
- /api/v1/foo
- /api/v2.0-Alpha/foo
- /api/v2015-05-01.3.0/foo
本文采用 /api/v1/foo
形式
安装依赖
需要安装这俩nuget包
- Asp.Versioning.Mvc
- Asp.Versioning.Mvc.ApiExplorer
注册服务
编辑 Program.cs
文件
builder.Services.AddApiVersioning(options => {
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("x-api-version"),
new MediaTypeApiVersionReader("ver")
);
})
.AddMvc()
.AddApiExplorer(options => {
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
以上代码做了这些事:
DefaultApiVersion
设置默认版本为1.0AssumeDefaultVersionWhenUnspecified
没有指定版本时,使用默认版本ReportApiVersions
在响应头里加上可用的接口版本ApiVersionReader
定义了可以从三个地方获取接口版本信息,URL里和俩请求头GroupNameFormat
指定了版本名称格式,详见下表SubstituteApiVersionInUrl
因为要使用URL指定版本,所以这里设置为true
API Version Format Strings
本文中我使用的是 'v'VVV
的格式
Format Specifier | Description | Examples |
---|---|---|
F | Full API version as [group version][.major[.minor]][-status] |
2017-05-01.1-RC -> 2017-05-01.1-RC |
FF | Full API version with optional minor version as [group version][.major[.minor,0]][-status] |
2017-05-01.1-RC -> 2017-05-01.1.0-RC |
G | Group version as yyyy-MM-dd | 2017-05-01.1-RC -> 2017-05-01 |
GG | Group version as yyyy-MM-dd with status | 2017-05-01.1-RC -> 2017-05-01-RC |
y | Group version year from 0 to 99 | 2001-05-01.1-RC -> 1 |
yy | Group version year from 00 to 99 | 2001-05-01.1-RC -> 01 |
yyy | Group version year with a minimum of three digits | 2017-05-01.1-RC -> 017 |
yyyy | Group version year as a four-digit number | 2017-05-01.1-RC -> 2017 |
M | Group version month from 1 through 12 | 2001-05-01.1-RC -> 5 |
MM | Group version month from 01 through 12 | 2001-05-01.1-RC -> 05 |
MMM | Group version abbreviated name of the month | 2001-06-01.1-RC -> Jun |
MMMM | Group version full name of the month | 2001-06-01.1-RC -> June |
d | Group version day of the month, from 1 through 31 | 2001-05-01.1-RC -> 1 |
dd | Group version day of the month, from 01 through 31 | 2001-05-01.1-RC -> 01 |
ddd | Group version abbreviated name of the day of the week | 2001-05-01.1-RC -> Mon |
dddd | Group version full name of the day of the week | 2001-05-01.1-RC -> Monday |
v | Minor version | 2001-05-01.1-RC -> 1 1.1 -> 1 |
V | Major version | 1.0-RC -> 1 2.0 -> 2 |
VV | Major and minor version | 1-RC -> 1 1.1-RC -> 1.1 1.1 -> 1.1 |
VVV | Major, optional minor version, and status | 1-RC -> 1-RC 1.1 -> 1.1 |
VVVV | Major, minor version, and status | 1-RC -> 1.0-RC 1.1 -> 1.1 1 -> 1.0 |
S | Status | 1.0-Beta -> Beta |
p | Padded minor version with default of two digits | 1.1 -> 01 1 -> 00 |
p[n] | Padded minor version with N digits | p2: 1.1 -> 01 p3: 1.1 -> 001 |
P | Padded major version with default of two digits | 2.1 -> 02 2 -> 02 |
P[n] | Padded major version with N digits | P2: 2.1 -> 02 P3: 2.1 -> 002 |
PP | Padded major and minor version with a default of two digits | 2.1 -> 02.01 2 -> 02.00 |
PPP | Padded major, optional minor version, and status with a default of two digits | 1-RC -> 01-RC 1.1-RC -> 01.01-RC |
PPPP | Padded major, minor version, and status with a default of two digits | 1-RC -> 01.00-RC 1.1-RC -> 01.01-RC |
设置API版本
例子接口有俩版本
- /api/v1/demo/test
- /api/v2/demo/test
在 Controller 下创建俩目录,v1 和 v2,然后分别创建Controller
上代码 Controllers/v1/DemoController.cs
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion(1.0)]
[ApiController]
public class DemoController : ControllerBase {
[HttpGet("[action]")]
public ApiResponse Test() {
return ApiResponse.Ok("version=1.0");
}
}
另一个版本的接口 Controllers/v2/DemoController.cs
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion(2.0)]
[ApiController]
public class DemoController : ControllerBase {
[HttpGet("[action]")]
public ApiResponse Test() {
return ApiResponse.Ok("version=2.0");
}
}
可以看到要区分不同版本的接口,只需要添加 [ApiVersion(2.0)]
特性即可。
因为我要使用URL来选择不同版本的接口,所以要把路由配置为 "api/v{version:apiVersion}/[controller]"
如果不把版本号写在URL里,也可以用请求参数传递,比如 /api/demo/test?api-version=1.0
这些可以在 ApiVersionReader
属性配置
配置Swagger
swagger基本已经是接口文档的标准了,但我发现很多文章都没有介绍swagger这块。(还好官方文档没有忘记)
首先创建一个配置类
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions> {
readonly IApiVersionDescriptionProvider provider;
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) =>
this.provider = provider;
public void Configure(SwaggerGenOptions options) {
foreach (var description in provider.ApiVersionDescriptions) {
options.SwaggerDoc(
description.GroupName,
new OpenApiInfo() {
Title = $"Example API {description.ApiVersion}",
Version = description.ApiVersion.ToString(),
});
}
}
}
注册服务
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
配置中间件
app.UseSwagger();
app.UseSwaggerUI(options => {
foreach (var description in app.DescribeApiVersions()) {
var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint(url, name);
}
});
效果 & 测试
搞定,访问swagger文档,在右上角接口分组可以看到不同版本
请求 https://localhost:7053/api/v1/Demo/Test
接口返回
{
"statusCode": 200,
"successful": true,
"message": "version=1.0",
"data": null,
"errorData": null
}
请求 https://localhost:7053/api/v2/Demo/Test
接口返回
{
"statusCode": 200,
"successful": true,
"message": "version=2.0",
"data": null,
"errorData": null
}
不错~ 😃