我们在开发 webapi 项目时如果遇到 api 接口需要同时支持多个版本的时候,比如接口修改了入参之后但是又希望支持老版本的前端(这里的前端可能是网页,可能是app,小程序 等等)进行调用,这种情况常见于 app,毕竟网页前端我们可以主动控制发布,只要统一发布后所有人的浏览器下一次访问网页时都会重新加载到最新版的代码,但是像 app 则无法保证用户一定会第一时间升级更新最新版的app,所以往往需要 api接口能够同时保持多个版本的逻辑,同支持新老版本的调用端app进行调用。

针对上面的描述举一个例子:

比如一个创建用户的接口,api/user/createuser

如果我们这个时候对该接口的入参和返回参数修改之后,但是又希望原本的 api/user/createuser 接口逻辑也可以正常运行,常见的做法有以下几种:

  1. 修改接口名称,将新的创建用户接口地址定义为 api/user/newcreateuser
  2. url传入版本标记,将新的创建用户接口地址定义为 api/user/createuser?api-version=2
  3. header传入版本标记,通过校验 header 中的 api-version 字段的值,用来区分调用不同版本的api

第一种方式的缺陷很明显,当接口版本多了之后接口的地址会定义很乱,本文主要讲解后面两种方法,如何在 asp.net webapi 项目中优雅的使用 header 或者 query 传入 版本标记,用来支持api的多个版本逻辑共存,并且扩展 Swagger 来实现 SwaggerUI 对于 api-version 的支持。

截至本文撰写时间,最新的 .net 版本为 .net6 ,本文中的所有示例也是基于 .net 6 来构建的。

首先创建一个 asp.net webapi 项目,本文使用 vs2022 直接创建 asp.net webapi 项目

项目创建好之后安装如下几个nuget包:

Swashbuckle.AspNetCore

Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer

注册 api 版本控制服务

            #region 注册 api 版本控制

            builder.Services.AddApiVersioning(options =>
{
//通过Header向客户端通报支持的版本
options.ReportApiVersions = true; //允许不加版本标记直接调用接口
options.AssumeDefaultVersionWhenUnspecified = true; //接口默认版本
//options.DefaultApiVersion = new ApiVersion(1, 0); //如果未加版本标记默认以当前最高版本进行处理
options.ApiVersionSelector = new CurrentImplementationApiVersionSelector(options); //配置为从 Header 传入 api-version
options.ApiVersionReader = new HeaderApiVersionReader("api-version"); //配置为从 Query 传入 api-version
//options.ApiVersionReader = new QueryStringApiVersionReader("api-version"); }); builder.Services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
}); #endregion
 

这里我们可以选择 api-version 版本标记的传入方式是从 url query 传递还是从 http header 传递。

移除项目默认的 swagger 配置

            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
 

采用如下 swagger 配置

            #region 注册 Swagger

            builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, SwaggerConfigureOptions>();

            builder.Services.AddSwaggerGen(options =>
{
options.OperationFilter<SwaggerOperationFilter>(); options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{typeof(Program).Assembly.GetName().Name}.xml"), true);
}); #endregion
 

其中用到了两个自定义的类 SwaggerConfigureOptions 和 SwaggerOperationFilter ,

SwaggerConfigureOptions 是一个自定义的 Swagger 配置方法,主要用于根据 api 控制器上的描述用来循环添加不同版本的 SwaggerDoc;

SwaggerOperationFilter 是一个自定义过滤器主要实现SwaggerUI 的版本参数 api-version 必填验证和标记过期的 api 的功能,具体内容如下

SwaggerConfigureOptions .cs

    /// <summary>
/// 配置swagger生成选项。
/// </summary>
public class SwaggerConfigureOptions : IConfigureOptions<SwaggerGenOptions>
{
readonly IApiVersionDescriptionProvider provider; public SwaggerConfigureOptions(IApiVersionDescriptionProvider provider) => this.provider = provider; public void Configure(SwaggerGenOptions options)
{
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); var modelPrefix = Assembly.GetEntryAssembly()?.GetName().Name + ".Models.";
var versionPrefix = description.GroupName + ".";
options.SchemaGeneratorOptions = new SchemaGeneratorOptions { SchemaIdSelector = type => (type.ToString()[(type.ToString().IndexOf("Models.") + 7)..]).Replace(modelPrefix, "").Replace(versionPrefix, "").Replace("`1", "").Replace("+", ".") };
}
} static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
var info = new OpenApiInfo()
{
Title = Assembly.GetEntryAssembly()?.GetName().Name,
Version = "v" + description.ApiVersion.ToString(),
//Description = "",
//Contact = new OpenApiContact() { Name = "", Email = "" }
}; if (description.IsDeprecated)
{
info.Description += "此 Api " + info.Version + " 版本已弃用,请尽快升级新版";
} return info;
}
}

SwaggerOperationFilter.cs

    /// <summary>
/// swagger 集成多版本api自定义设置
/// </summary>
public class SwaggerOperationFilter : IOperationFilter
{ public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var apiDescription = context.ApiDescription; //判断接口遗弃状态,对接口进行标记调整
operation.Deprecated |= apiDescription.IsDeprecated(); if (operation.Parameters == null)
{
return;
} //为 api-version 参数添加必填验证
foreach (var parameter in operation.Parameters)
{
var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); if (parameter.Description == null)
{
parameter.Description = description.ModelMetadata?.Description;
} if (parameter.Schema.Default == null && description.DefaultValue != null)
{
parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString());
} parameter.Required |= description.IsRequired;
}
}
}
 

这些都配置完成之后,开始对 控制模块进行调整

为了方便代码的版本区分,所以我这里在 Controllers 下按照版本建立的独立的文件夹 v1 和 v2

然后在 v1 和 v2 的文件夹下防止了对于的 Controllers,如下图的结构

然后只要在对应文件夹下的控制器头部加入版本标记

[ApiVersion("1")] [ApiVersion("2")] [ApiVersion("......")]

如下图的两个控制器

​ 这样就配置好了两个版本的 UserController 具体控制器内部的代码可以不同,然后运行 项目观察 Swagger UI 就会发现如下图:

​ 可以通过 SwaggerUI 右上角去切换各个版本的 SwaggerDoc

​点击单个接口的 Try it out 时接口这边也同样会出现一个 api-version 的字段,因为我们这边是配置的从 Header 传入该参数所以从界面中可以看出该字段是从 Header 传递的,如果想要从 url 传递,主要调整上面 注册 api 版本控制服务 那边的设置为从 Query 传入即可。

至此基础的 api 版本控制逻辑就算完成了。

下面衍生讲解一下如果 项目中有部分 api 控制器并不需要版本控制,是全局通用的如何处理,有时候我们一个项目中总会存在一些基础的 api 是基本不会变的,如果每次 api 版本升级都把所有的 控制器都全部升级显然太过繁琐了,所以我们可以把一些全局通用的控制器单独标记出来。

只要在这些控制器头部添加 [ApiVersionNeutral] 标记即可,添加了 [ApiVersionNeutral] 标记的控制器则表明该控制器退出了版本控制逻辑,无论 app 前端传入的版本号的是多少,都可以正常进入该控制的逻辑。如下

    [ApiVersionNeutral]
[ApiController]
[Route("api/[controller]")]
public class FileController : ControllerBase
{ }
 

还有一种就是当我们的 api 版本升级之后,我们希望标记某个 api 已经是弃用的,则可以使用 Deprecated 来表示该版本的 api 已经淘汰。

    [ApiVersion("1", Deprecated = true)]
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{ [HttpPost("CreateUser")]
public void CreateUser(DtoCreateUser createUser)
{ //内部注册逻辑此处省略
} }
 

添加淘汰标记之后运行 SwaggerUI 就会出现下图的样式

​ 通过 SwaggerDoc 就可以很明确的看出 v1 版本的 api 已经被淘汰了。

至此 关于asp.net core webapi 中 api 版本控制的基本操作就讲解完了,有任何不明白的,可以在文章下面评论或者私信我,欢迎大家积极的讨论交流。

.net webapi 实现 接口版本控制并打通swagger支持的更多相关文章

  1. 使用Swashbuckle.AspNetCore生成.NetCore WEBAPI的接口文档

    一.问题 使用Swashbuckle.AspNetCore生成.NetCore WEBAPI的接口文档的方法 二.解决方案 参考文章:https://docs.microsoft.com/zh-cn/ ...

  2. Net Core WebApi几种版本控制对比

    Net Core WebApi几种版本控制对比 一.版本控制的好处: (1)有助于及时推出功能, 而不会破坏现有系统. (2)它还可以帮助为选定的客户提供额外的功能. API 版本控制可以采用不同的方 ...

  3. Asp.Net WebAPI配置接口返回数据类型为Json格式

    Asp.Net WebAPI配置接口返回数据类型为Json格式   一.默认情况下WebApi 对于没有指定请求数据类型类型的请求,返回数据类型为Xml格式 例如:从浏览器直接输入地址,或者默认的XM ...

  4. c# WebApi之接口返回类型详解

    c# WebApi之接口返回类型详解 https://blog.csdn.net/lwpoor123/article/details/78644998

  5. ASP.NET WebApi服务接口如何防止重复请求实现HTTP幂等性

    一.背景描述与课程介绍 明人不说暗话,跟着阿笨一起玩WebApi.在我们平时开发项目中可能会出现下面这些情况; 1).由于用户误操作,多次点击网页表单提交按钮.由于网速等原因造成页面卡顿,用户重复刷新 ...

  6. SpringBoot系统列 5 - 接口版本控制、SpringBoot FreeMarker模板引擎

    接着上篇博客的代码继续写 1.接口版本控制 一个系统上线后会不断迭代更新,需求也会不断变化,有可能接口的参数也会发生变化,如果在原有的参数上直接修改,可能会影响线上系统的正常运行,这时我们就需要设置不 ...

  7. 接口文档神器Swagger(下篇)

    本文来自网易云社区 作者:李哲 二.Swagger-springmvc原理解析 上面介绍了如何将springmvc和springboot与swagger结合,通过简单配置生成接口文档,以及介绍了swa ...

  8. 接口文档神器Swagger(上篇)

    本文来自网易云社区 作者:李哲 接口文档管理一直是一个让人头疼的问题,伴随着各种接口文档管理平台涌现,如阿里开源的rap,ShowDoc,sosoapi,等等(网上能找到很多这种管理平台,包括我们自己 ...

  9. .Net Core 分布式微服务框架 - Jimu 添加 Swagger 支持

    系列文章 .Net Core 分布式微服务框架介绍 - Jimu .Net Core 分布式微服务框架 - Jimu 添加 Swagger 支持 一.前言 最近有空就优化 Jimu (一个基于.Net ...

随机推荐

  1. 最佳实践 | 通过使用 Jira Service Management 改进 HR 工作流程

    ​​ Jira Service Management 承诺解锁高速团队.技术团队和与之合作的业务部门都可以从 Jira Service Management中受益,尤其是 HR 团队. Atlassi ...

  2. 基于dhtmlxGantt的Blazor甘特图组件

    基于dhtmlxGantt实现的甘特图组件,目前仅做到了数据展现,方法及插槽暂未实现,若需可按照dhtmlxGantt的文档及微软的Balzor文档,自行扩展. 数据发生变化后甘特图会立即发生变化. ...

  3. opencv学习之边缘检测

    边缘检测 是图像处理 过程中经常会涉及到的一个环节.而在计算机视觉 和 机器学习领域,边缘检测 用于 特征提取 和 特征检测 效果也是特别明显.而 openCV 中进行边缘检测的 算法 真是五花八门, ...

  4. SQL多表多字段比对方法

    目录 表-表比较 整体思路 找出不同字段的明细 T1/T2两表ID相同的部分,是否存在不同NAME 两表的交集与差集:判断两表某些字段是否相同 两表的交集与差集:找出T2表独有的id 字段-字段比较 ...

  5. MySQL UNION 操作符用于连接两个以上的 SELECT 语句的结果组合到一个结果集合中

    union 会删除重复数据 union all 不会删除重复数据 select * from ( select *,'a' as kind from tablea where name is not ...

  6. Python 中的内存管理

    Python 中一切皆对象,这些对象的内存都是在运行时动态地在堆中进行分配的,就连 Python 虚拟机使用的栈也是在堆上模拟的.既然一切皆对象,那么在 Python 程序运行过程中对象的创建和释放就 ...

  7. python之模块(os、sys、json、subprocess)

    目录 os模块 sys模块 json模块 subprocess模块 os模块 os模块主要是与操作系统打交道. 导入os模块 import os 创建单层文件夹,路径必须要存在 os.mkdir(路径 ...

  8. Mysql优化基础之Explain工具

    字段解释 id:代表sql中查询语句的序列号,序列号越大则执行的优先级越高,序号一样谁在前谁先执行.id为null则最后执行 select_type:查询类型,表示当前被分析的sql语句的查询的复杂度 ...

  9. python基础学习5

    Python的基础学习5 内容概要 流程控制理论 if判断 while循环 内容详情 流程控制理论 # 流程控制:即控制事物执行的流程 # 执行流程的分类 1.顺序结构 从上往下按顺序依次执行 2.分 ...

  10. camunda开源版与商业版的差异

    Camunda流程引擎分社区版和企业版,社区版实际上是开源版,是Apache2.0协议,企业版实际上是商业收费版本,需要购买授权才能使用,那么社区版和企业版的差异有哪些呢,社区版本是否能满足我们日常的 ...