前言

作为开发人员,我们经常向应用程序添加新功能并修改当前的 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联合使用。

虽然aspnet-api-versioning提供了多种版本控制的方式,但是在我们实际项目开发的过程中,我们尽可能只采用一种方案,只用一种标准可以让我们版本开发更加的容易维护,而且多种方案配置默认策略 对 OpenApi 的集成和版本控制的默认行为都互有影响。

以上四种方案只有QueryStringApiVersionReaderUrlSegmentApiVersionReader符合 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的模式。

AddApiExplorerOpenApi 对接口格式化的策略配置

认识 几个核心方法

  • 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 PathhttpHeader 等传参数的方式只需要微调就可以实现,更多高级功能请浏览aspnet-api-versioning官网(文末有官网地址)。

Api 版本控制是设计现代 Api 的最佳实践之一。从第一个版本开始实现 Api 版本控制,这样可以更容易地让客户端支持未来的 Api 版本,同时也让您的团队习惯于管理破坏性变化和对 Api 进行版本控制。

以下是本文的完整 源代码

aspnet-api-versioning 官网学习文档

.Net接口版本管理与OpenApi的更多相关文章

  1. 使用Swagger和OpenAPI 3规范定义API接口并集成到SpringBoot

    1. OpenAPI 3 规范介绍及属性定义 参考官方定义:https://swagger.io/specification/ 2. 使用OpenAPI 3规范定义API接口 官方样例参考:https ...

  2. 【API】获取优酷视频信息接口

    序:        自己的网站中需要接入一个视频模块,虚拟主机的空间小所以只能引用第三方的链接.感觉国内优酷好不错,所以查了一下优酷的接口. 0x00:        先去优酷API开放中心申请一个开 ...

  3. 公交线路免费api接口代码

    描写叙述:本接口主要是依据城市名称 +  线路名称 模糊查找城市公交线路信息. 开源api接口:http://openapi.aibang.com/bus/lines?app_key=keyvalue ...

  4. 详解功能版本管理之使用eoLinker

    先看一个对话: "这里,你改一下,这里返回一个object." "好...好......" "还有这里,返回个String." ...... ...

  5. 图灵机器人API接口

    调用图灵API接口实现人机交互 流程一: 注册 图灵机器人官网: http://www.tuling123.com/ 第一步: 先注册, 然后创建机器人, 拿到一个32位的key 编码方式 UTF-8 ...

  6. REST接口设计规范

    URI格式规范 URI(Uniform Resource Identifiers) 统一资源标示符 URL(Uniform Resource Locator) 统一资源定位符 URI的格式定义如下: ...

  7. 开源的api文档管理系统

    api文档 php 在项目中,需要协同开发,所以会写许多API文档给其他同事,以前都是写一个简单的TXT文本或Word文档,口口相传,这种方式比较老土了,所以,需要有个api管理系统专门来管理这些ap ...

  8. AI 基础

    what AI ? 人工智能(Artificial Intelligence),英文缩写为AI. 人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的 ...

  9. 利用python itchat给女朋友定时发信息

    利用itchat给女朋友定时发信息 涉及到的技术有itchat,redis,mysql,最主要的还是mysql咯,当然咯,这么多东西,我就只介绍我代码需要用到的,其他的,如果需要了解的话,就需要看参考 ...

  10. python 全栈开发,Day123(图灵机器人,web录音实现自动化交互问答)

    昨日内容回顾 . 百度ai开放平台 . AipSpeech技术,语言合成,语言识别 . Nlp技术,短文本相似度 . 实现一个简单的问答机器人 . 语言识别 ffmpeg (目前所有音乐,视频领域,这 ...

随机推荐

  1. C/C++ 进程线程操作技术

    手动创建单进程: 下面通过一个实例来分别演示进程的创建函数. #include <windows.h> #include <stdio.h> BOOL WinExec(char ...

  2. spark读取空orc文件时报错java.lang.RuntimeException: serious problem at OrcInputFormat.generateSplitsInfo

    问题复现: G:\bigdata\spark-2.3.3-bin-hadoop2.7\bin>spark-shell 2020-12-26 10:20:48 WARN NativeCodeLoa ...

  3. 如何在 macOS Sonoma 虚拟机中安装 VMware Tools

    vmware-tools VMware Tools 简介 VMware Tools 中包含一系列服务和模块,可在 VMware 产品中实现多种功能,从而使用户能够更好地管理客户机操作系统,以及与客户机 ...

  4. ehlib 排序后 滚动条 不对问题

    针对CDS的情况,作者的代码里 好像也不知道什么原因 必须写Cds.First但是,不写的话,就是不行,去掉作者的注释就可以了.

  5. Idhttp Post https 连接 报“IOHandler value is not valid.”错误

    今天研究阿里巴巴的对接,发现IDHTTP 的post 如果是 https 的连接就会报:"IOHandler value is not valid."错误 加载https的站点页面 ...

  6. 亚马逊Dynamo数据库解读(英文版)

    最近看了亚麻的Dynamo,个人认为其中always writeable的业务目标,对于DHT,vector clock,merkel tree的应用,包括对于一致性和高可用的权衡(基于CAP猜想,实 ...

  7. LGV引理

    LGV引理是用来统计DAG中固定若干起点和终点情况下的选择不相交链的方案数的. 同样用来优化计数问题,但是比Pólya定理友好多了,这也就是为什么它能够被直接糊到NOI考场上. 对于一张DAG,每条边 ...

  8. react 八千字长文深入了解react合成事件底层原理,原生事件中阻止冒泡是否会阻塞合成事件?

    壹 ❀ 引 在前面两篇文章中,我们花了较大的篇幅介绍react的setState方法,在介绍setState同步异步时提到,在react合成事件中react对于this.state更新都是异步,但在原 ...

  9. Educational Codeforces Round 136 (Rated for Div. 2) A-E

    比赛链接 A 题解 知识点:模拟. 所有点都跑一遍即可. 另外可以不模拟, \(\geq 2*2\) 的情况都可以摆在 \((2,2)\) 这个点,其他摆在 \((1,1)\) . 时间复杂度 \(O ...

  10. 构建SatelliteRpc:基于Kestrel的RPC框架(整体设计篇)

    背景 之前在.NET 性能优化群内交流时,我们发现很多朋友对于高性能网络框架有需求,需要创建自己的消息服务器.游戏服务器或者物联网网关.但是大多数小伙伴只知道 DotNetty,虽然 DotNetty ...