ToB企服应用市场:ToB评测及商务社交产业平台

标题: .Net接口版本管理与OpenApi [打印本页]

作者: 干翻全岛蛙蛙    时间: 2024-5-14 02:34
标题: .Net接口版本管理与OpenApi
前言

作为开发职员,我们经常向应用程序添加新功能并修改当前的 Api。版本控制使我们能够安全地添加新功能而不会造成中断性变动。一个精良的 Api 版本控制策略可以清晰地传达所做的更改,并允许使用现有 REST Api 的客户端在预备好时才迁移或更新他们的应用程序到最新版本。
哪些行为可能会造成 Api 的中断性变动呢?

我们在做开发的过程中迟早会面临 Api 版本控制需求,在 Api 开发的过程中学习如何进行版本控制是至关重要的。
本文主要介绍在 MinimalApis 进行版本控制,官网文档在文末
借助aspnet-api-versioning 资助 Minimalapis 实现版本控制

开始之前在项目中安装两个 nuget 包:
  1. Install-Package Asp.Versioning.Http
  2. Install-Package Asp.Versioning.Mvc.ApiExplorer
复制代码

配置详情
  1. builder.Services.AddApiVersioning(options =>
  2. {
  3.     options.DefaultApiVersion = new ApiVersion(2, 0);//默认版本
  4.     options.ReportApiVersions = true;//Response Header 指定可用版本
  5.     options.AssumeDefaultVersionWhenUnspecified = true;//如果没有指定版本用默认配置
  6.     options.ApiVersionReader = ApiVersionReader.Combine(
  7.       new QueryStringApiVersionReader("api-version"),//QueryString
  8.       new HeaderApiVersionReader("X-Version"),//Header
  9.       new MediaTypeApiVersionReader("ver"),//Accept MediaType
  10.       new UrlSegmentApiVersionReader());//Route Path
  11. }).AddApiExplorer(options =>
  12. {
  13.     options.GroupNameFormat = "'v'VVV";
  14.     options.SubstituteApiVersionInUrl = true;
  15. });
复制代码
AddApiVersioning 提供了一个委托参数 Action来对 Api 版本控制配置,下面看主要参数的配置解释

虽然aspnet-api-versioning提供了多种版本控制的方式,但是在我们现实项目开发的过程中,我们尽可能只采用一种方案,只用一种尺度可以让我们版本开发更加的轻易维护,而且多种方案配置默认策略 对 OpenApi 的集成和版本控制的默认行为都互有影响。
以上四种方案只有QueryStringApiVersionReader和UrlSegmentApiVersionReader符合 Microsoft REST Guidelines 的规范,所以我们只需要上面选一个即可.
MinimalApis 版本控制

我们采用其中的一种 来做演示看看 ApiVesioning 是如何实现的,就按默认行为 QueryStringApiVersionReader 来做一个简单的 Demo。
创建一个 MinimalApi 的项目
VS 创建新项目->输入项目名字然后点击下一步-> 使用控制器的 CheckBox 确定取消勾选
.Net Cli 安装 nuget 或者 VS 包管理器
  1. dotnet add package Asp.Versioning.Http
  2. dotnet add package Asp.Versioning.Mvc.ApiExplorer
复制代码
Program.cs 添加默认配置
  1. builder.Services.AddProblemDetails();
  2. builder.Services.AddApiVersioning(options =>
  3. {
  4.     options.DefaultApiVersion = new ApiVersion(2, 0);//默认版本
  5.     options.ReportApiVersions = true;//Response Header 指定可用版本
  6.     options.AssumeDefaultVersionWhenUnspecified = true;//如果没有指定版本用默认配置
  7. }).AddApiExplorer(options =>
  8. {
  9.     options.GroupNameFormat = "'v'VVV";
  10.     options.SubstituteApiVersionInUrl = true;
  11. });
复制代码
aspnet-api-versioning的非常处理机制依赖ProblemDetails,
所以builder.Services.AddProblemDetails();必须要注册到 IOC 容器。
AddApiVersioning 没有注册任何的ApiVersionReader,所以会用默认的QueryStringApiVersionReader的模式。
AddApiExplorer 是 OpenApi 对接口格式化的策略配置
熟悉 几个核心方法


添加 Api EndPoint
  1. {
  2.     var todoV1 = app.NewVersionedApi("Todo")
  3.          .HasDeprecatedApiVersion(new ApiVersion(1, 0));//过期版本
  4.     var todoGroup = todoV1.MapGroup("/api/Todo");
  5.     todoGroup.MapGet("/", () => "Version 1.0").WithSummary("请用V2版本代替");
  6.     todoGroup.MapGet("sayhello", (string name) => $"hello {name}").
  7. }
  8. {
  9.     var todoV2 = app.NewVersionedApi("Todo")
  10.                          .HasApiVersion(new ApiVersion(2, 0));
  11.     var todoGroup = todoV2.MapGroup("/api/Todo");
  12.     todoGroup.MapGet("/", () => "Version 2.0").MapToApiVersion(new ApiVersion(2, 0)).WithSummary("Version2");
  13. }
  14. {
  15.     var todoV3 = app.NewVersionedApi("Todo")
  16.             .HasApiVersion(new ApiVersion(3, 0));
  17.     var todoGroup = todoV3.MapGroup("/api/Todo");
  18.     todoGroup.MapGet("/", () => "Version 3.0").WithSummary("Version3");
  19.     todoGroup.MapGet("sayhello", (string name) => $"hello {name}").IsApiVersionNeutral();
  20. }
复制代码
上面定义 Todo 的相关业务,当前有三个版本,V1 已颠末期不推荐使用,V2 是主要版本,V3 是预览开发版本,IsApiVersionNeutral标注了一个sayHello接口是跟版本无关的
Run 项目 测试一下

访问 api/Todo,Options 配置了默认版本为 2.0
https://localhost:7141/api/todo 返回 Version 2.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 (版本无关)
到这儿基本可以实现我们的需求了,在aspnet-api-versioning中还提供了NewApiVersionSet的方法配置添加实现 Api 的管理,大家也可以尝试下。
版本管理对接 OpenApi

刚才我们的项目 Run 起来之后 Swagger 首页看到只有 V1 版本的界面,我们来设置一下让他支持 Swagger 界面版本切换
创建 ConfigureSwaggerOptions 添加多个 SwaggerDoc
  1. public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions<SwaggerGenOptions>
  2. {
  3.     public void Configure(SwaggerGenOptions options)
  4.     {
  5.         foreach (var description in provider.ApiVersionDescriptions)
  6.         {
  7.             options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
  8.         }
  9.     }
  10.     private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
  11.     {
  12.         var text = new StringBuilder("An example application with OpenAPI, Swashbuckle, and API versioning.");
  13.         var info = new OpenApiInfo()
  14.         {
  15.             Title = "MinimalApis With OpenApi ",
  16.             Version = description.ApiVersion.ToString(),
  17.             Contact = new OpenApiContact() { Name = "Ruipeng", Email = "478083649@qq.com" },
  18.             License = new OpenApiLicense() { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") }
  19.         };
  20.         if (description.IsDeprecated)
  21.         {
  22.             text.Append(" This API version has been deprecated.");
  23.         }
  24.         if (description.SunsetPolicy is SunsetPolicy policy)
  25.         {
  26.             if (policy.Date is DateTimeOffset when)
  27.             {
  28.                 text.Append(" The API will be sunset on ")
  29.                     .Append(when.Date.ToShortDateString())
  30.                     .Append('.');
  31.             }
  32.             if (policy.HasLinks)
  33.             {
  34.                 text.AppendLine();
  35.                 for (var i = 0; i < policy.Links.Count; i++)
  36.                 {
  37.                     var link = policy.Links[i];
  38.                     if (link.Type == "text/html")
  39.                     {
  40.                         text.AppendLine();
  41.                         if (link.Title.HasValue)
  42.                         {
  43.                             text.Append(link.Title.Value).Append(": ");
  44.                         }
  45.                         text.Append(link.LinkTarget.OriginalString);
  46.                     }
  47.                 }
  48.             }
  49.         }
  50.         info.Description = text.ToString();
  51.         return info;
  52.     }
  53. }
复制代码
依赖注入
  1. builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
复制代码
创建拦截器
  1. public class SwaggerDefaultValues : IOperationFilter
  2. {
  3.     public void Apply(OpenApiOperation operation, OperationFilterContext context)
  4.     {
  5.         var apiDescription = context.ApiDescription;
  6.         operation.Deprecated |= apiDescription.IsDeprecated();
  7.         foreach (var responseType in context.ApiDescription.SupportedResponseTypes)
  8.         {
  9.             var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString();
  10.             var response = operation.Responses[responseKey];
  11.             foreach (var contentType in response.Content.Keys)
  12.             {
  13.                 if (!responseType.ApiResponseFormats.Any(x => x.MediaType == contentType))
  14.                 {
  15.                     response.Content.Remove(contentType);
  16.                 }
  17.             }
  18.         }
  19.         if (operation.Parameters is null)
  20.         {
  21.             return;
  22.         }
  23.         foreach (var parameter in operation.Parameters)
  24.         {
  25.             var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
  26.             parameter.Description ??= description.ModelMetadata?.Description;
  27.             if (parameter.Schema.Default is null &&
  28.                  description.DefaultValue is not null &&
  29.                  description.DefaultValue is not DBNull &&
  30.                  description.ModelMetadata is ModelMetadata modelMetadata)
  31.             {
  32.                 var json = JsonSerializer.Serialize(description.DefaultValue, modelMetadata.ModelType);
  33.                 parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json);
  34.             }
  35.             parameter.Required |= description.IsRequired;
  36.         }
  37.     }
  38. }
复制代码
Swagger 依赖注入
  1. builder.Services.AddSwaggerGen(options => options.OperationFilter<SwaggerDefaultValues>());
复制代码
UseSwaggerUI 添加 Swagger 终结点
  1.     app.UseSwaggerUI(options =>
  2.     {
  3.         var descriptions = app.DescribeApiVersions();
  4.         // build a swagger endpoint for each discovered API version
  5.         foreach (var description in descriptions)
  6.         {
  7.             var url = $"/swagger/{description.GroupName}/swagger.json";
  8.             var name = description.GroupName.ToUpperInvariant();
  9.             options.SwaggerEndpoint(url, name);
  10.         }
  11.     });
复制代码
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 官网学习文档

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4