首页 > 其他分享 >.Net接口版本管理与OpenApi

.Net接口版本管理与OpenApi

时间:2024-03-14 09:58:00浏览次数:33  
标签:接口 OpenApi api 版本 Api var new Net options

前言

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


测试 V1 版本
https://localhost:7141/api/todo?api-version=1.0
返回 Version 1.0 符合预期且 ResponseHeader 标记了过期版本和受支持的版本
image

image


测试 V2 版本
https://localhost:7141/api/todo?api-version=2.0
可以看到 返回 Version 2.0 符合预期
image


测试 V3 版本
https://localhost:7141/api/todo?api-version=3.0
可以看到 返回 Version 3.0 符合预期
image


测试 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
    image

到这儿基本可以实现我们的需求了,在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 = "[email protected]" },
            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 查看项目

image

左上角可以成功切换版本,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 官网学习文档

标签:接口,OpenApi,api,版本,Api,var,new,Net,options
From: https://www.cnblogs.com/ruipeng/p/18072151

相关文章

  • iftop监视网络接口的实时流量
    iftop监视网络接口的实时流量iftop-B是一个命令行工具,用于实时监控网络流量。在执行该命令时,-B参数用于以字节为单位显示流量信息。具体来说,iftop工具可以用来监视网络接口的实时流量情况,包括每个连接的源地址、目标地址、传输速率、传输量等信息。它以交互式的方式显示流......
  • 界面控件DevExpress ASP.NET Scheduler - 助力快速交付个人信息管理系统(下)
    DevExpressASP.NETScheduler组件能完全复制MicrosoftOutlookScheduler的样式和功能,具有日、周、月和时间轴视图,并包括内置的打印支持,因此用户可以在尽可能短的时间内交付全功能的个人信息管理系统。在上文中(点击这里回顾>>)主要介绍了DevExpressASP.NETScheduler组件的日、......
  • 使用ScottPlot库在.NET WinForms中快速实现大型数据集的交互式显示
    前言在.NET应用开发中数据集的交互式显示是一个非常常见的功能,如需要创建折线图、柱状图、饼图、散点图等不同类型的图表将数据呈现出来,帮助人们更好地理解数据、发现规律,并支持决策和沟通。本文我们将一起来学习一下如何使用ScottPlot库在.NETWinForms中快速实现大型数据集的交......
  • 世界银行使用.NET 7开发的免费电子问卷制作系统Survey Solution
    SurveySolution(下文简称SS)是世界银行数据部开发的一套免费电子问卷制作系统,官网地址为:https://mysurvey.solutions/,github地址:https://github.com/surveysolutions/该系统具有以下几个主要特点:通过内置模版可以轻松地制作一系列传统问卷题型,同时还可以实现层级结构......
  • Asp .Net Core 系列:Asp .Net Core 集成 Hangfire+MySQL
    简介https://www.hangfire.io/在.NET和.NETCore应用程序中执行后台处理的简单方法,无需Windows服务或单独的进程。Hangfire是一个开源的.NET任务调度框架,它提供了内置集成化的控制台,允许用户直观明了地查看作业调度情况。Hangfire不需要依赖于单独的应用程序执行(如Wi......
  • Adaptive Diffusion in Graph Neural Networks论文阅读笔记
    AdaptiveDiffusioninGraphNeuralNetworks论文阅读笔记Abstract​ 最近提出了图扩散卷积(GDC),利用广义图扩散来扩展传播邻域。然而,GDC中的邻域大小是通过在验证集上进行网格搜索来手动对每个图进行调整的,这使得其泛化实际上受到了限制。为了解决这个问题,我们提出了自适应扩散......
  • 深度学习——LeNet卷积神经网络初探
    LeNet--卷积神经网络初探模型介绍:简单介绍:从网络结构可以看出LeNet对于现在的大模型来说是一个非常小的神经网络,他一共由7个层顺序连接组成。分别是卷积层、pooling层、卷积层、pooling层和三个全连接层。用现代的深度学习框架来实现代码如下:代码实现和解读:net=nn.Sequent......
  • Java 错误 java.net.ConnectException
    本篇文章介绍了Java中的java.net.ConnectException错误。Java中的java.net.ConnectException错误java.net.ConnectException是使用网络时最常见的异常。它主要发生在客户端、应用程序和服务器之间建立TCP连接时。它是一个经过检查的应用程序,可以使用try-cat......
  • JMeter接口性能压测之阶梯加压线程组(Stepping Thread Group)
    一、前言1、阶梯式场景(负载测试):该场景主要应用在负载测试只里面,通过设定一定的并发线程数,给定加压规则,遵循“缓起步,快结束”的原则,不断地增加并发用户来找到系统的性能瓶颈,进而有针对性的进行各方面的系统优化。2、Stepping Thread Group的作用减少服务器的瞬时压力,......
  • 1688 API商品详情接口与ERP系统应用
     API接口与ERP系统集成的应用主要包括数据同步、业务流程自动化和信息共享三个方面。数据同步:通过API接口,ERP系统可以与其他系统之间进行数据的交换和同步。比如,将销售订单从电商平台自动导入到ERP系统中,然后将发货信息同步回电商平台,实现订单管理的自动化。又如,将供应商的......