我们在开发 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. python实现基于smtp发送邮件

    [前言] 在某些项目中,我们需要实现发送邮件的功能,比如: 爬虫结束后,发送邮件通知 定时发送邮件提醒待办事项 某项业务逻辑触发邮件通知 今天我们就分享如何基于smtp借助163邮箱来发送邮件 [实现 ...

  2. 探索ABP的EventHub解决方案

    在上一章中,我们构建了一个简单的全栈 Web 应用程序,我们已经看到了使用 ABP 框架开发应用的典型流程,在接下来,我们将使用 ABP 框架创建更高级的应用程序. 给出具有现实世界复杂性的例子并不容 ...

  3. jQuery操作标签,jQuery事件操作,jQuery动画效果,前端框架

    jQuery操作标签 jQuery代码查找标签绑定的变量名推荐使用 $xxxEle 样式类操作 addClass();// 添加指定的CSS类名. removeClass();// 移除指定的CSS类 ...

  4. 利用ArcEngine开发地图发布服务,将mxd文档一键发布成wmts,并根据需要对地图进行空间查询,返回客户端geojson

    一直想开发一个软件取代ArcGIS Server,该软件使用ArcEngine开发,以Windows Service形式发布,部署在服务端上,解决wmts地图服务发布和空间查询的问题,经过不断的研究. ...

  5. OAuth2.0之OLTU实现举例

    一.场景 三个角色:用户(user),web应用(client),资源服务器和授权服务器合为服务器(server) 用户登录登录后可查看自己的信息 二.准备 2.1 数据库 schema drop t ...

  6. MySQL分库分表-理论

    分库分表的几种方式 把一个实例中的多个数据库拆分到不同的实例 把一个库中的表分离到不同的数据库中 数据库分片前的准备 在数据库并发和负载没有达到限制时,不推荐水平拆分 对一个库中的相关表进行水平拆分到 ...

  7. linux篇-linux下源码安装nginx

    LNMP模式 后续继续更新,先搭建nginx 安装环境gcc gcc-c++ 2 下载源码包解压 配置第一个报错 安装openssl openssl-devel yum -y install open ...

  8. 好客租房41-react组件基础综合案例-渲染列表数据

    1渲染列表 在state定义数据 进行数据渲染 //导入react import React from 'react' import ReactDOM from 'react-dom' //导入组件 ...

  9. [C++STL] 队列 queue 的入门

    队列结构 概念: 队列(queue):和栈相似,也是一种特殊的线性表.和栈不同的是,队列只允许在表的一端进行插入操作,而在另一端进行删除操作.一般来说,进行插入操作的一端称为队尾,进行删除操作的一端称 ...

  10. Hadoop常见shell命令

    Hadoop中常见的shell命令 1.如何将Linux本地的数据上传到HDFS中? hadoop fs -put 本地的文件 HDFS中的目录hdfs dfs -put 本地的文件 HDFS中的目录 ...