我们在开发 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. OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: "ip": executable file not found in $PATH: unknown (Docker容器没有ip addr命令:exec ip addr 报错)

    一.报错 1.报错信息1: OCI runtime exec failed: exec failed: container_linux.go:380: starting container proce ...

  2. 开发一个不需要重写成Hive QL的大数据SQL引擎

    摘要:开发一款能支持标准数据库SQL的大数据仓库引擎,让那些在Oracle上运行良好的SQL可以直接运行在Hadoop上,而不需要重写成Hive QL. 本文分享自华为云社区<​​​​​​​​​ ...

  3. react实战系列 —— 我的仪表盘(bizcharts、antd、moment)

    其他章节请看: react实战 系列 My Dashboard 上一篇我们在 spug 项目中模仿"任务计划"模块实现一个类似的一级导航页面("My任务计划") ...

  4. Java-GUI 编程之 JList、JComboBox实现列表框

    目录 JList.JComboBox实现列表框 简单列表框 不强制存储列表项的ListModel和ComboBoxModel 强制存储列表项的DefaultListModel和DefaultCombo ...

  5. psexec.py规避杀软

    前言 在内网渗透中,当获取到一个账号密码后,经常会使用impacket套件中的psexec.py进行远程连接并执行命令,但是因为用的人多了,杀软也对psexec.py特征进行了拦截,也就导致了如果使用 ...

  6. Glide源码解析二---into方法

    转载请标明出处,维权必究: https://www.cnblogs.com/tangZH/p/12543154.html Glide作为一个强大的图片加载框架,已经被android官方使用,所以,明白 ...

  7. 【java并发编程】Lock & Condition 协调同步生产消费

    一.协调生产/消费的需求 本文内容主要想向大家介绍一下Lock结合Condition的使用方法,为了更好的理解Lock锁与Condition锁信号,我们来手写一个ArrayBlockingQueue. ...

  8. Kafka 基础概念及架构

    一.Kafka 介绍 Kafka是⼀个分布式.分区的.多副本的.多⽣产者.多订阅者,基于zookeeper协调的分布式⽇志系统(也可以当做MQ系统),常⻅可以⽤于web/nginx⽇志.访问⽇志,消息 ...

  9. 原创工具14Finger-全能web指纹识别与分享平台

    14Finger 功能齐全的Web指纹扫描和分享平台,基于vue3+django前后端分离的web架构,并集成了长亭出品的rad爬虫的功能,内置了一万多条互联网开源的指纹信息. Github:http ...

  10. 项目实战:Qt+OpenCV大家来找茬(Qt抓图,穿透应用,识别左右图区别,框选区别,微调位置)

    前言   本项目的出现理由只是笔者的一个念头,于是利用专业Qt和Opencv相关的知识开发一个辅助工具,本文章仅用于Qt和Opencv结合的学习.   Demo演示效果          运行包下载地 ...