Asp-Net-Core开发笔记:API版本管理

asp,net,core,开发,笔记,api,版本,管理 · 浏览次数 : 564

小编点评

生成内容时需要带简单的排版,例如以下内容: ``` ... foreach (var description in provider.ApiVersionDescriptions) { options.SwaggerDoc( description.GroupName, new OpenApiInfo() { Title = $\"Example API {description.ApiVersion}\", Version = description.ApiVersion.ToString(), }); } } ... ``` ``` ... foreach (var description in provider.ApiVersionDescriptions) { options.SwaggerDoc( description.GroupName, new OpenApiInfo() { Title = $\"Example API {description.ApiVersion}\", Version = description.ApiVersion.ToString(), }); } } ... ``` 此外,还要注意以下内容: * 使用 `[ApiVersion(2.0)]` 特性来指定接口版本。 * 使用 `foreach` 循环遍历 API版本描述,并设置 `options.SwaggerDoc()` 方法。 * 使用 `options.SwaggerDoc()` 方法设置 API文档信息,包括标题、版本、描述等。 * 使用 `foreach` 循环遍历 API描述,并设置 `options.SwaggerDoc()` 方法设置 API文档信息。 * 使用 `app.UseSwagger()` 方法注册中间件。 * 使用 `app.UseSwaggerUI()` 方法注册 Swagger UI。 * 使用 `options.SwaggerEndpoint()` 方法设置 API文档的 URL。 * 使用 `options.SwaggerDoc()` 方法设置 API文档的标题。 * 使用 `options.SwaggerDoc()` 方法设置 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.0
  • AssumeDefaultVersionWhenUnspecified 没有指定版本时,使用默认版本
  • 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
}

不错~ 😃

参考资料

与Asp-Net-Core开发笔记:API版本管理相似的内容:

Asp-Net-Core开发笔记:API版本管理

## 前言 对于Web API应用程序而言,随着时间的推移以及需求的增加或改变,API必然会遇到升级的需求。事实上,Web API应用程序应该从创建时就考虑到API版本的问题。业务的调整、功能的增加、接口的移除与改名、接口参数变动、实体属性的添加、删除和更改等都会改变API的功能,从而带来版本的变更

Asp-Net-Core开发笔记:使用ActionFilterAttribute实现非侵入式的参数校验

前言 在现代应用开发中,确保API的安全性和可靠性至关重要。 面向切面编程(AOP)通过将横切关注点(如验证、日志记录、异常处理)与核心业务逻辑分离,极大地提升了代码的模块化和可维护性。 在ASP.NET Core中,利用ActionFilterAttribute可以方便地实现AOP的理念,能够以简

Asp-Net-Core开发笔记:使用原生的接口限流功能

前言 之前介绍过使用 AspNetCoreRateLimit 组件来实现接口限流 从 .Net7 开始,AspNetCore 开始内置限流组件,当时我们的项目还在 .Net6 所以只能用第三方的 现在都升级到 .Net8 了,当然是得来试试这个原生组件 体验后:配置使用都比较简单,不过功能也没有 A

Asp-Net-Core开发笔记:给SwaggerUI加上登录保护功能

前言 在 SwaggerUI 中加入登录验证,是我很早前就做过的,不过之前的做法总感觉有点硬编码,最近 .Net8 增加了一个新特性:调用 MapSwagger().RequireAuthorization 来保护 Swagger UI ,但官方的这个功能又像半成品一样,只能使用 postman c

Asp-Net-Core开发笔记:进一步实现非侵入性审计日志功能

前言 上次说了利用 AOP 思想实现了审计日志功能,不过有同学反馈还是无法实现完全无侵入,于是我又重构了一版新的。 回顾一下:Asp-Net-Core开发笔记:实现动态审计日志功能 现在已经可以实现对业务代码完全无侵入的审计日志了,在需要审计的接口上加上 [AuditLog] 特性,就可以记录这个接

Asp-Net-Core开发笔记:EFCore统一实体和属性命名风格

前言 C# 编码规范中,类和属性都是大写驼峰命名风格(PascalCase / UpperCamelCase),而在数据库中我们往往使用小写蛇形命名(snake_case),在默认情况下,EFCore会把原始的类名和属性名直接映射到数据库,这不符合数据库的命名规范。 为了符合命名规范,而且也为了看起

Asp-Net-Core开发笔记:快速在已有项目中引入EFCore

前言 很多项目一开始选型的时候没有选择EFCore,不过EFCore确实好用,也许由于种种原因后面还是需要用到,这时候引入EFCore也很方便。 本文以 StarBlog 为例,StarBlog 目前使用的 ORM 是 FreeSQL ,引入 EFCore 对我来说最大的好处是支持多个数据库,如果是

Asp-Net-Core开发笔记:使用RateLimit中间件实现接口限流

前言 最近一直在忙(2月份沉迷steam,3月开始工作各种忙),好久没更新博客了,不过也积累了一些,忙里偷闲记录一下。 这个需求是这样的,我之前做了个工单系统,现在要对登录、注册、发起工单这些功能做限流,不能让用户请求太频繁。 从 .Net7 开始,已经有内置的限流功能了,但目前我们的项目还在使用

Asp-Net-Core开发笔记:FrameworkDependent搭配docker部署

## 前言 之前我写过一篇使用 docker 部署 AspNetCore 应用的文章,这种方式搭配 CICD 非常方便, build 之后 push 到私有的 dockerhub ,在生产服务器上 pull 下来镜像就可以直接运行了。 然而,有时需要一种更传统的部署方式,比如在本地打包可执行文件之后

Asp-Net-Core学习笔记:gRPC快速入门

## 前言 此前,我在做跨语言调用时,用的是 Facebook 的 Thrift,挺轻量的,还不错。 >Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用(RPC)框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的。它通过一个代码