1前言
對于Web API應用程式而言,随着時間的推移以及需求的增加或改變,API必然會遇到更新的需求。事實上,Web API應用程式應該從建立時就考慮到API版本的問題。業務的調整、功能的增加、接口的移除與改名、接口參數變動、實體屬性的添加、删除和更改等都會改變API的功能,進而帶來版本的變更。
現有的資料大部分是使用
Microsoft.AspNetCore.Mvc.Versioning
這個包,但我實際使用的時候發現這個包早就不更新了,微軟官方文檔好像也沒有這部分介紹,不過在這個包的nuget首頁上有說已經換成新的
Asp.Versioning.Mvc
包,原來是微軟改名部發力了,失敬失敬~ 😂
好在這個新的包在Github上有很詳細的文檔,但這改名速度實在是猛,為了實作這個功能,我走了不少彎路。😂
OK,本文基于 .Net6.0,以
AspNetCore WebApi
為例,介紹引入API版本管理的過程。
2基礎
指定版本的方法有兩種,既可以使用[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
形式
3安裝依賴
需要安裝這倆nuget包
- Asp.Versioning.Mvc
- Asp.Versioning.Mvc.ApiExplorer
4注冊服務
編輯
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;
});
以上代碼做了這些事:
-
設定預設版本為1.0DefaultApiVersion
-
沒有指定版本時,使用預設版本AssumeDefaultVersionWhenUnspecified
-
在響應頭裡加上可用的接口版本ReportApiVersions
-
定義了可以從三個地方擷取接口版本資訊,URL裡和倆請求頭ApiVersionReader
-
指定了版本名稱格式,詳見下表GroupNameFormat
-
因為要使用URL指定版本,是以這裡設定為trueSubstituteApiVersionInUrl
API Version Format Strings
本文中我使用的是
'v'VVV
的格式
Format Specifier | Description | Examples |
---|---|---|
F | Full API version as | 2017-05-01.1-RC -> 2017-05-01.1-RC |
FF | Full API version with optional minor version as | 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 |
5設定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
屬性配置
6配置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);
}
});
7效果 & 測試
搞定,通路swagger文檔,在右上角接口分組可以看到不同版本
請求
https://localhost:7053/api/v1/Demo/Test
接口傳回
{
"statusCode": 200,
"successful": true,
"message": "version=1.0",
"data": ,
"errorData":
}
請求
https://localhost:7053/api/v2/Demo/Test
接口傳回
{
"statusCode": 200,
"successful": true,
"message": "version=2.0",
"data": ,
"errorData":
}
不錯~ 😃
8參考資料
- https://github.com/dotnet/aspnet-api-versioning/wiki