Asp-Net-Core開發(fā)筆記:API版本管理
1 前言
對于Web API應(yīng)用程序而言,隨著時間的推移以及需求的增加或改變,API必然會遇到升級的需求。事實上,Web API應(yīng)用程序應(yīng)該從創(chuàng)建時就考慮到API版本的問題。業(yè)務(wù)的調(diào)整、功能的增加、接口的移除與改名、接口參數(shù)變動、實體屬性的添加、刪除和更改等都會改變API的功能,從而帶來版本的變更。
現(xiàn)有的資料大部分是使用 Microsoft.AspNetCore.Mvc.Versioning 這個包,但我實際使用的時候發(fā)現(xiàn)這個包早就不更新了,微軟官方文檔好像也沒有這部分介紹,不過在這個包的nuget主頁上有說已經(jīng)換成新的 Asp.Versioning.Mvc 包,原來是微軟改名部發(fā)力了,失敬失敬~ ??
好在這個新的包在Github上有很詳細(xì)的文檔,但這改名速度實在是猛,為了實現(xiàn)這個功能,我走了不少彎路。??
OK,本文基于 .Net6.0,以 AspNetCore WebApi 為例,介紹引入API版本管理的過程。
2 基礎(chǔ)
指定版本的方法有兩種,既可以使用[ApiVersion]特性,也可以使用版本約定方式。當(dāng)定義了不同版本的API接口后,客戶端可以通過如下多種方式來訪問某一版本的API。
- 使用URL路徑,如 api/v1.0/values
- 使用查詢字符串,如 api/values?api-version=1.0
- 使用HTTP自定義消息頭
- 使用媒體類型(Media Type)參數(shù),如 Accept: application/json;v=2.0
ASP.NET Core MVC默認(rèn)的方式是使用查詢字符串,查詢字符串使用的參數(shù)名為api-version。具體使用哪種方式由服務(wù)端指定(用下面介紹的 ApiVersionReader 屬性來配置),既可以使用其中的一種,也可以同時使用多種不同的方式。
API版本的格式由主版本號與次版本號組成,此外還可以包含可選的兩部分:版本組和狀態(tài)。
-
[Version Group.]. [-Status] -
[ [.Minor]][-Status]
版本組的格式為YYYY-MM-DD,它能夠?qū)PI接口起到邏輯分組的作用,狀態(tài)則能夠標(biāo)識當(dāng)前版本的狀況,如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 注冊服務(wù)
編輯 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設(shè)置默認(rèn)版本為1.0 -
AssumeDefaultVersionWhenUnspecified沒有指定版本時,使用默認(rèn)版本 -
ReportApiVersions在響應(yīng)頭里加上可用的接口版本 -
ApiVersionReader定義了可以從三個地方獲取接口版本信息,URL里和倆請求頭 -
GroupNameFormat指定了版本名稱格式,詳見下表 -
SubstituteApiVersionInUrl因為要使用URL指定版本,所以這里設(shè)置為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 |
5 設(shè)置API版本
例子接口有倆版本
- /api/v1/demo/test
- /api/v2/demo/test
在 Controller 下創(chuàng)建倆目錄,v1 和 v2,然后分別創(chuàng)建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");
??}
}
可以看到要區(qū)分不同版本的接口,只需要添加 [ApiVersion(2.0)] 特性即可。
因為我要使用URL來選擇不同版本的接口,所以要把路由配置為 "api/v{version:apiVersion}/[controller]"
如果不把版本號寫在URL里,也可以用請求參數(shù)傳遞,比如 /api/demo/test?api-version=1.0
這些可以在 ApiVersionReader 屬性配置
6 配置Swagger
swagger基本已經(jīng)是接口文檔的標(biāo)準(zhǔn)了,但我發(fā)現(xiàn)很多文章都沒有介紹swagger這塊。(還好官方文檔沒有忘記)
首先創(chuàng)建一個配置類
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(),
????????});
????}
??}
}
注冊服務(wù)
builder.Services.AddTransient,?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":?null,
??"errorData":?null
}
請求 https://localhost:7053/api/v2/Demo/Test
接口返回
{
??"statusCode":?200,
??"successful":?true,
??"message":?"version=2.0",
??"data":?null,
??"errorData":?null
}
不錯~ ??
8 參考資料
- https://github.com/dotnet/aspnet-api-versioning/wiki
