.Net接口版本管理与OpenApi
前言
作为开发人员,我们经常向应用程序添加新功能并修改当前的 Api
。版本控制使我们能够安全地添加新功能而不会造成中断性变更。一个良好的 Api
版本控制策略可以清晰地传达所做的更改,并允许使用现有 REST Api
的客户端在准备好时才迁移或更新他们的应用程序到最新版本。
哪些行为可能会造成 Api 的中断性变更呢?
- 删除或重命名 Api
- 修改 Api 参数(类型,名称,可选参数变成非可选参数,删除必需参数等)
- 更改现有 Api 的行为
- 更改 Api 响应
- 更改 Api 错误代码
- More
我们在做开发的过程中迟早会面对 Api 版本控制需求,在 Api 开发的过程中学习如何进行版本控制是至关重要的。
本文主要介绍在 MinimalApis
进行版本控制,官网文档在文末
借助aspnet-api-versioning
帮助 Minimalapis
实现版本控制
开始之前在项目中安装两个 nuget 包:
Install-Package Asp.Versioning.Http
Install-Package Asp.Versioning.Mvc.ApiExplorer
- Asp.Versioning.Http 用于在
MinimalApis
中提供版本控制支持
- Asp.Versioning.Mvc.ApiExplorer 用于
OpenApi
,格式化路由版本参数等
配置详情
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(2, 0);//默认版本
options.ReportApiVersions = true;//Response Header 指定可用版本
options.AssumeDefaultVersionWhenUnspecified = true;//如果没有指定版本用默认配置
options.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("api-version"),//QueryString
new HeaderApiVersionReader("X-Version"),//Header
new MediaTypeApiVersionReader("ver"),//Accept MediaType
new UrlSegmentApiVersionReader());//Route Path
}).AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
AddApiVersioning
提供了一个委托参数 Action<ApiVersioningOptions>
来对 Api 版本控制配置
,下面看主要参数的配置解释
DefaultApiVersion
options.DefaultApiVersion = new ApiVersion(2,0);
指定 Api 的默认版本以上设置为 2.0 版本,默认是 1.0
ReportApiVersions
options.ReportApiVersions = true;//Response Header 指定可用版本
在 ResponseHeader 中指定当前 Api 可用的版本 默认不开启
AssumeDefaultVersionWhenUnspecified
options.AssumeDefaultVersionWhenUnspecified = true;
开启后 如果
Api
不指定版本默认DefaultApiVersion
设置版本,适合已经存在的服务开启版本控制,帮助在不破坏现有客户端的情况下改进现有服务并集成正式的Api
。ApiVersionReader
options.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("api-version"),//QueryString 默认参数 api-vesion
new HeaderApiVersionReader("X-Version"),//Header 默认v
new MediaTypeApiVersionReader("ver"),//Accept MediaType 默认参数v
new UrlSegmentApiVersionReader());//Route Path 参数v
配置如何读取客户端指定的 Api 版本,默认为
QueryStringApiVersionReader
即使用名为api-version
的查询字符串参数。
从上面可以看出有四种开箱即用的 Api 服务版本定义方式- QueryStringApiVersionReader:
https://localhost:7196/api/Todo?api-version=1
- HeaderApiVersionReader:
https://localhost:7196/api/Todo -H 'X-Version: 1'
- MediaTypeApiVersionReader:
GET api/helloworld HTTP/2
Host: localhost
Accept: application/json;ver=1.0
- UrlSegmentApiVersionReader:
https://localhost:7196/api/workouts?api-version=1
可以通过
ApiVersionReader.Combine
联合使用。- QueryStringApiVersionReader:
虽然
aspnet-api-versioning
提供了多种版本控制的方式,但是在我们实际项目开发的过程中,我们尽可能只采用一种方案,只用一种标准可以让我们版本开发更加的容易维护,而且多种方案配置默认策略 对OpenApi
的集成和版本控制的默认行为都互有影响。
以上四种方案只有
QueryStringApiVersionReader
和UrlSegmentApiVersionReader
符合 Microsoft REST Guidelines 的规范,所以我们只需要上面选一个即可.
MinimalApis 版本控制
我们采用其中的一种 来做演示看看 ApiVesioning
是如何实现的,就按默认行为 QueryStringApiVersionReader 来做一个简单的 Demo。
创建一个 MinimalApi 的项目
VS 创建新项目->输入项目名字然后点击下一步-> 使用控制器的 CheckBox 确定取消勾选
.Net Cli 安装 nuget 或者 VS 包管理器
dotnet add package Asp.Versioning.Http
dotnet add package Asp.Versioning.Mvc.ApiExplorer
Program.cs
添加默认配置
builder.Services.AddProblemDetails();
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(2, 0);//默认版本
options.ReportApiVersions = true;//Response Header 指定可用版本
options.AssumeDefaultVersionWhenUnspecified = true;//如果没有指定版本用默认配置
}).AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
aspnet-api-versioning的异常处理机制依赖ProblemDetails
,
所以builder.Services.AddProblemDetails();
必须要注册到 IOC 容器。
AddApiVersioning
没有注册任何的ApiVersionReader
,所以会用默认的QueryStringApiVersionReader
的模式。
AddApiExplorer
是 OpenApi
对接口格式化的策略配置
认识 几个核心方法
- NewVersionedApi :创建一个路由组建造者,用于定义 Api 中所有版本化端点。
HasApiVersion :表示 ApiVersionSet 支持指定的 ApiVersion。
HasDeprecatedApiVersion :配置废弃指定的 Api 版本。
MapToApiVersion : 将指定的 Api 版本映射到配置的端点。
IsApiVersionNeutral : 版本无关 也可以说任何的版本都可以访问到这个终结点
添加 Api EndPoint
{
var todoV1 = app.NewVersionedApi("Todo")
.HasDeprecatedApiVersion(new ApiVersion(1, 0));//过期版本
var todoGroup = todoV1.MapGroup("/api/Todo");
todoGroup.MapGet("/", () => "Version 1.0").WithSummary("请用V2版本代替");
todoGroup.MapGet("sayhello", (string name) => $"hello {name}").
}
{
var todoV2 = app.NewVersionedApi("Todo")
.HasApiVersion(new ApiVersion(2, 0));
var todoGroup = todoV2.MapGroup("/api/Todo");
todoGroup.MapGet("/", () => "Version 2.0").MapToApiVersion(new ApiVersion(2, 0)).WithSummary("Version2");
}
{
var todoV3 = app.NewVersionedApi("Todo")
.HasApiVersion(new ApiVersion(3, 0));
var todoGroup = todoV3.MapGroup("/api/Todo");
todoGroup.MapGet("/", () => "Version 3.0").WithSummary("Version3");
IsApiVersionNeutral();
}
上面定义 Todo 的相关业务,当前有三个版本,V1 已经过期不推荐使用,V2 是主要版本,V3 是预览开发版本,IsApiVersionNeutral
标注了一个sayHello
接口是跟版本无关的
Run 项目 测试一下
访问 api/Todo
,Options 配置了默认版本为 2.0
https://localhost:7141/api/todo
返回 Version 1.0 符合预期
测试 V1 版本
https://localhost:7141/api/todo?api-version=1.0
返回 Version 1.0 符合预期且 ResponseHeader 标记了过期版本和受支持的版本
测试 V2 版本
https://localhost:7141/api/todo?api-version=2.0
可以看到 返回 Version 2.0 符合预期
测试 V3 版本
https://localhost:7141/api/todo?api-version=3.0
可以看到 返回 Version 3.0 符合预期
测试 sayHello (版本无关)
https://localhost:7141/api/Todo/sayhello
https://localhost:7141/api/Todo/sayhello?name=ruipeng& api-vesion=1.0
https://localhost:7141/api/Todo/sayhello?name=ruipeng&api-vesion=2.0
https://localhost:7141/api/Todo/sayhello?name=ruipeng&api-vesion=3.0
到这儿基本可以实现我们的需求了,在
aspnet-api-versioning
中还提供了NewApiVersionSet
的方法配置添加实现Api
的管理,大家也可以尝试下。
版本管理对接 OpenApi
刚才我们的项目 Run
起来之后 Swagger
首页看到只有 V1
版本的界面,我们来设置一下让他支持 Swagger
界面版本切换
创建 ConfigureSwaggerOptions 添加多个 SwaggerDoc
public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions<SwaggerGenOptions>
{
public void Configure(SwaggerGenOptions options)
{
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
}
}
private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
var text = new StringBuilder("An example application with OpenAPI, Swashbuckle, and API versioning.");
var info = new OpenApiInfo()
{
Title = "MinimalApis With OpenApi ",
Version = description.ApiVersion.ToString(),
Contact = new OpenApiContact() { Name = "Ruipeng", Email = "478083649@qq.com" },
License = new OpenApiLicense() { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") }
};
if (description.IsDeprecated)
{
text.Append(" This API version has been deprecated.");
}
if (description.SunsetPolicy is SunsetPolicy policy)
{
if (policy.Date is DateTimeOffset when)
{
text.Append(" The API will be sunset on ")
.Append(when.Date.ToShortDateString())
.Append('.');
}
if (policy.HasLinks)
{
text.AppendLine();
for (var i = 0; i < policy.Links.Count; i++)
{
var link = policy.Links[i];
if (link.Type == "text/html")
{
text.AppendLine();
if (link.Title.HasValue)
{
text.Append(link.Title.Value).Append(": ");
}
text.Append(link.LinkTarget.OriginalString);
}
}
}
}
info.Description = text.ToString();
return info;
}
}
依赖注入
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
创建拦截器
public class SwaggerDefaultValues : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var apiDescription = context.ApiDescription;
operation.Deprecated |= apiDescription.IsDeprecated();
foreach (var responseType in context.ApiDescription.SupportedResponseTypes)
{
var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString();
var response = operation.Responses[responseKey];
foreach (var contentType in response.Content.Keys)
{
if (!responseType.ApiResponseFormats.Any(x => x.MediaType == contentType))
{
response.Content.Remove(contentType);
}
}
}
if (operation.Parameters is null)
{
return;
}
foreach (var parameter in operation.Parameters)
{
var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
parameter.Description ??= description.ModelMetadata?.Description;
if (parameter.Schema.Default is null &&
description.DefaultValue is not null &&
description.DefaultValue is not DBNull &&
description.ModelMetadata is ModelMetadata modelMetadata)
{
var json = JsonSerializer.Serialize(description.DefaultValue, modelMetadata.ModelType);
parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json);
}
parameter.Required |= description.IsRequired;
}
}
}
Swagger 依赖注入
builder.Services.AddSwaggerGen(options => options.OperationFilter<SwaggerDefaultValues>());
UseSwaggerUI 添加 Swagger 终结点
app.UseSwaggerUI(options =>
{
var descriptions = app.DescribeApiVersions();
// build a swagger endpoint for each discovered API version
foreach (var description in descriptions)
{
var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint(url, name);
}
});
Run
Swagger 查看项目
左上角可以成功切换版本,OpenApi 版本管理成功
最后
本文的 demo
用了aspnet-api-versioning
版本控制的一种方式来做的演示,WebApi Controller
配置好 Options
之后只需要用aspnet-api-versioning
提供的 Attribute
就可以实现版本管理,Route Path
和 httpHeader
等传参数的方式只需要微调就可以实现,更多高级功能请浏览aspnet-api-versioning
官网(文末有官网地址)。
Api
版本控制是设计现代 Api
的最佳实践之一。从第一个版本开始实现 Api
版本控制,这样可以更容易地让客户端支持未来的 Api
版本,同时也让您的团队习惯于管理破坏性变化和对 Api
进行版本控制。
以下是本文的完整 源代码
aspnet-api-versioning 官网学习文档
.Net接口版本管理与OpenApi的更多相关文章
- 使用Swagger和OpenAPI 3规范定义API接口并集成到SpringBoot
1. OpenAPI 3 规范介绍及属性定义 参考官方定义:https://swagger.io/specification/ 2. 使用OpenAPI 3规范定义API接口 官方样例参考:https ...
- 【API】获取优酷视频信息接口
序: 自己的网站中需要接入一个视频模块,虚拟主机的空间小所以只能引用第三方的链接.感觉国内优酷好不错,所以查了一下优酷的接口. 0x00: 先去优酷API开放中心申请一个开 ...
- 公交线路免费api接口代码
描写叙述:本接口主要是依据城市名称 + 线路名称 模糊查找城市公交线路信息. 开源api接口:http://openapi.aibang.com/bus/lines?app_key=keyvalue ...
- 详解功能版本管理之使用eoLinker
先看一个对话: "这里,你改一下,这里返回一个object." "好...好......" "还有这里,返回个String." ...... ...
- 图灵机器人API接口
调用图灵API接口实现人机交互 流程一: 注册 图灵机器人官网: http://www.tuling123.com/ 第一步: 先注册, 然后创建机器人, 拿到一个32位的key 编码方式 UTF-8 ...
- REST接口设计规范
URI格式规范 URI(Uniform Resource Identifiers) 统一资源标示符 URL(Uniform Resource Locator) 统一资源定位符 URI的格式定义如下: ...
- 开源的api文档管理系统
api文档 php 在项目中,需要协同开发,所以会写许多API文档给其他同事,以前都是写一个简单的TXT文本或Word文档,口口相传,这种方式比较老土了,所以,需要有个api管理系统专门来管理这些ap ...
- AI 基础
what AI ? 人工智能(Artificial Intelligence),英文缩写为AI. 人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的 ...
- 利用python itchat给女朋友定时发信息
利用itchat给女朋友定时发信息 涉及到的技术有itchat,redis,mysql,最主要的还是mysql咯,当然咯,这么多东西,我就只介绍我代码需要用到的,其他的,如果需要了解的话,就需要看参考 ...
- python 全栈开发,Day123(图灵机器人,web录音实现自动化交互问答)
昨日内容回顾 . 百度ai开放平台 . AipSpeech技术,语言合成,语言识别 . Nlp技术,短文本相似度 . 实现一个简单的问答机器人 . 语言识别 ffmpeg (目前所有音乐,视频领域,这 ...
随机推荐
- 又学了一招:微软科普Windows 11电脑自动清理释放硬盘
你是不是每次都等到电脑内存被占满,磁盘"红"成一片,才想起来去清理那些没用的程序or文件? 今天微软官方科普了一个小技巧:既然都用上了Windows 11 ,为什么不让电脑帮你自动 ...
- 【奶奶看了都会】ComfyUI+SVD制作AI视频教程,附效果演示
AI一天,人间一年 大家好啊,我是小卷,最近AI绘画又发展出一些新玩意了,小卷因为工作的关系有一个月没关注AI的发展了,都有点跟不上版本节奏了... 1.comfyui的使用效果 今天给大家介绍下AI ...
- Kafka-常用命令行命令(Kafak3.4.0最新命令)
第一章 Kafka常用命令 1. Topic(主题) 1.1. 创建Topic bin/kafka-topics.sh --create --bootstrap-server hadoop01:909 ...
- NC14700 追债之旅
题目链接 题目 题目描述 小明现在要追讨一笔债务,已知有n座城市,每个城市都有编号,城市与城市之间存在道路相连(每条道路都是双向的),经过任意一条道路需要支付费用.小明一开始位于编号为1的城市,欠债人 ...
- 承前启后,Java对象内存布局和对象头
承前启后,Java对象内存布局和对象头 大家好,我是小高先生.在我之前的一篇文章<并发编程防御装-锁(基础版)>中,我简要介绍了锁的基础知识,并解释了为什么Java中的任何对象都可以作为锁 ...
- Spring异步任务async介绍与案例实战
关于spring异步任务 简单地说,用@Async注释bean的方法将使其在单独的线程中执行.换句话说,调用者不会等待被调用方法的完成.利用spring提供的注解即可简单轻松的实现异步任务处理. 默认 ...
- Java I/O 教程(十 一) BufferedWriter和BufferedReader
Java BufferedWriter 类 Java BufferedWriter class 继承了Writer类,为Writer实例提供缓冲. 提升了写字符和字符串性能. 类定义: public ...
- 自定义组件WebComponents加HTML模板template元素及shadowDOM影子DOM及定义一些事件
自定义组件WebComponents加HTML模板template元素及shadowDOM影子DOM及定义一些事件 Web Components 自定义组件,可以自定义一个类似于div的元素,里面的事 ...
- 2024-02-28:用go语言,有一个由x轴和y轴组成的坐标系, “y下“和“y上“表示一条无限延伸的道路,“y下“表示这个道路的下限,“y上“表示这个道路的上限, 给定一批长方形,每一个长方形有(
2024-02-28:用go语言,有一个由x轴和y轴组成的坐标系, "y下"和"y上"表示一条无限延伸的道路,"y下"表示这个道路的下限,& ...
- 【Azure 媒体服务】使用编码预设文件(Preset.json)来自定义编码任务 -- 创建视频缩略图
问题描述 在Azure门户上创建Transform Encoding时候,只能选择 Built-in Preset 编码方式(如:H265ContentAwareEncoding) 在创建编码任务时, ...