4

.NET 6当中的Web API版本控制 - 张飞洪[厦门]

 1 year ago
source link: https://www.cnblogs.com/jackyfei/p/16710817.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进。

为了了解ASP.NET Core Web API的版本控制,我们必须了解API中的一些版本控制策略,然后将API版本控制与OpenAPI集成,以便我们可以在Swagger UI中看到版本化的API。

1 版本控制及策略

1.1 什么是API版本控制?

API版本控制的目的是为了解决接口运维的问题。随着时间推移,我们希望对那些调用API的前端人员,都有一个固定不变的API调用规则和策略。因为需求会变化,业务会增长,如果我们对API的设计没有进行版本控制,那么依赖API的用户将变得无所适从,加上团队人员的变迁,这会大大降低我们的联调效率。
这就是我们为什么要进行API版本控制的目的所在。那么,我们如何对API进行版本化呢?

1.2 API版本控制策略

我们这里讨论三种最常用的API版本控制策略。
1)URI路径版本控制
URI路径策略很受欢迎,因为它更易于实现。一般我们会在URI路径的某个地方插入一个版本指示符,如v1或v2,如下所示:
https://iot.com/api/v1/products
以上是版本1,如果要升级为版本2,我们直接将v1改成v2即可:
https://iot.com/api/v2/products
注意在切换API版本时,为了获得正确的API返回的内容,原来的URI作为缓存键可能会失效。基于路径的版本控制很通用,几乎大部分的平台或者语言都支持这种方法,几乎成为了一种默认的标准,我们的案例代码默认也是采用这种策略。
2)Header版本控制
使用Header(头部)进行版本控制,头部一个谓词,并且有一个头部值,该值就是调用者需要分辨的版本号,如以下示例内容:
GET /api/products HTTP/1.1 Host: localhost:5001 Content-Type: application/json x-api-version: 2
此策略有个好处是它不会污染URI。但是,在客户端使用这些类型的API会比较麻烦一些。
3)查询字符串版本控制
查询字符串(Query string)根据API的使用者的需要,使用查询字符串指定API的版本。,如果请求中没有查询字符串,则应该具有API的隐式默认版本。我们看一个示例:
https://iot.com/api/products?api-version=2
以上三种策略都有各自的使用场景,具体应该选择哪一个,取决于消费方法以及未来的规划。

1.3 废弃的API

我们可能会碰到一种需求,就是希望告知API调用方,哪些API不再推荐使用。比如一旦某个API版本在未来几个月没有人使用,我们希望删除该API:
[ApiVersion("1.0", Deprecated = true)]
具体使用很简单,这是Microsoft.AspNetCore.Mvc名称空间下的使用方式,凡是加上这种特性的API都会别废弃使用。
以上我们了解API版本控制的一些理论介绍,接下来我们通过代码来实现版本控制,以及如何将它们与OpenAPI集成方便在Swagger UI中查看。

2 API版本控制与OpenAPI的集成

2.1 API版本控制

本文是基于我视频的项目代码,所以在下面的代码连贯性上可能对您会有影响,但是整体上不影响您的理解。
如果您想查看完整的代码,可以订阅我的视频,不胜感觉。

为了通过代码实现版本控制,我们需要切换到Iot.WebApi项目下进行,我们先在该项目下安装两个NuGet包:

dotnet add package Microsoft.AspNetCore.Mvc.Versioning
dotnet add package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer

第一个包是基于ASP.NET Core Mvc的版本服务,第二个包用于查找URL和HTTP方法、查找Controller(控制器)和Action元数据的一些功能。
接着,我们在Controller目录下创建两个文件夹,v1和v2,我们原先创建的控制器全部默认迁移到v1下,并修改一下相关的名称空间,原来是:
Iot.WebApi.Controllers
现在改成:
Iot.WebApi.Controllers.v1
然后,我们修改抽象基类ApiContoller头部的特性:

[ApiVersion("1.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public abstract class ApiController : ControllerBase
{
    private IMediator _mediator;
    protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService<IMediator>();
}

我们添加了一个ApiVersion特性,并指定版本号,更新Route为动态API版本,所有继承该基类的控制器都会标记上版本号。
我们还可以通过Deprecated来弃用WeatherForecast接口:

namespace Travel.WebApi.Controllers.v1 {     
[ApiVersion("1.0", Deprecated = true)]     
public class WeatherForecastController : ApiController { …} }

废弃了一个接口,我们一般会创建一个新的接口版本,我们在v2文件夹下创建一个新的WeatherForecast.cs文件,代码如下所示:

namespace Travel.WebApi.Controllers.v2 {     
  [ApiVersion("2.0")]     
  [ApiController]     
  [Route("api/v{version:apiVersion}/[controller]")]     
  public class WeatherForecastController : ControllerBase     
  {       
    …         
    [HttpPost]         
    public IEnumerable<WeatherForecast> Post(string city) {     
      var rng = new Random();             
      return Enumerable.Range(1,5).Select(index => new WeatherForecast
      {                 
        …                 
        City = city}).ToArray();
      }     
  } 
}

新旧接口的主要区别是HTTP方法,在版本1中,必须发送一个GET请求以获取日期和温度数据,而在版本2中,必须使用查询参数city发送一个POST请求。
因此,API必须具有版本控制,以避免中断第一个版本的API导致的问题。
带有查询的POST请求不是好的做法,因为它是非幂等的,而GET、PUT和DELETE用于幂等请求。这里先将就用着。

2.2 OpenAPI

我们先在Iot.WebApi的根目录中创建一个新文件夹并命名为Helpers。然后创建两个C#文件,SwagerOptions.cs和SwaggerDefaultValue.cs,ConfigureSwaggerOptions.cs如下所示:

using System; 
using Microsoft.AspNetCore.Mvc.ApiExplorer; 
using Microsoft.Extensions.DependencyInjection; 
using Microsoft.Extensions.Options; 
using Microsoft.OpenApi.Models; 
using Swashbuckle.AspNetCore.SwaggerGen; 
namespace IoT.WebApi.OpenApi {     
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>  
{ 
  …  
  public void Configure(SwaggerGenOptions options) {…}         
  private static OpenApiInfo CreateInfoForApiVersion (ApiVersionDescription description) {…}     
  } 
}

这里有两个方法:Configure和OpenApiInfo,下面是Configure方法的代码块:

public void Configure(SwaggerGenOptions options)         
{             
  foreach (var description in _provider.ApiVersionDescriptions)                    
  options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); 
}

Configure方法的作用是为每个新发现的API版本添加一个Swagger文档。下面是OpenApiInfo方法的代码块:

private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
    var info = new OpenApiInfo
    {
        Title = "Travel Tour",
        Version = description.ApiVersion.ToString(),
        Description = "Web Service for Travel Tour.",
        Contact = new OpenApiContact
        {
            Name = "IT Department",
            Email = "[email protected]",
            Url = new Uri("https://appstv6elnt7382.h5.xiaoeknow.com/")
        }
    };
 
    if (description.IsDeprecated)
        info.Description += " <strong>该API版本已经过期.</strong>";
    return info;
}

此代码用于Swagger相关信息设置,如应用程序的标题、版本、描述、联系人姓名、联系人电子邮件和URL。
我们再看下SwaggerDefaultValues.cs:

public class SwaggerDefaultValues : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
           //
    }
}

SwaggerDefaultValues 会重写并替换Startup.cs中的services.AddSwaggerGen()。下面是Apply方法的代码:

var apiDescription = context.ApiDescription;
operation.Deprecated |= apiDescription.IsDeprecated();
 
if (operation.Parameters == null)
    return;
 
foreach (var parameter in operation.Parameters)
{
    var description = apiDescription.ParameterDescriptions.First(
        pd => pd.Name == parameter.Name);
 
    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;
}

Apply方法允许Swagger生成器添加API资源管理器的所有相关元数据。
接下来我们更新一下Startup.cs文件,在ConfigureServices中找到AddSwageGen方法,然后使用下面代码进行替换:

services.AddSwaggerGen(c =>
{
    c.OperationFilter<SwaggerDefaultValues>();
});

这里使用过滤器配置我们之前创建的SwaggerDefaultValue。接下来在AddSwaggerGen方法后面给ConfigureSwaggerOptions设置服务生命周期:
services.AddTransient<IConfigureOptions, ConfigureSwaggerOptions>();
我们还要添加对ApiVersioning的注册(Microsoft.AspNetCore.Mvc.Versioning):

services.AddApiVersioning(config =>
{
    config.DefaultApiVersion = new ApiVersion(1, 0);
    config.AssumeDefaultVersionWhenUnspecified = true;
    config.ReportApiVersions = true;
});

上面的代码在服务集合中添加了版本控制,包括定义默认API版本和API支持的版本。
我们继续在AddApiVersioning下面添加API Explorer(Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer):

services.AddVersionedApiExplorer(options =>
{
    options.GroupNameFormat = "'v'VVV";
});

该代码添加了一个API资源管理器,它的格式:'v'major[.minor][status] 。
现在在Configure方法中添加一个参数。将其命名为provider,类型为IApiVersionDescriptionProvider,如下所示:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
这里涉及到的是有关API版本的信息,我们看下Configure中的UseSwaggerUI方法:

app.UseSwaggerUI(c =>
{
    foreach (var description in provider.ApiVersionDescriptions)
    {
        c.SwaggerEndpoint(
            $"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
    }
});

以上通过循环为每个发现的API版本构建一个Swagger访问地址。
现在,让我们运行程序并查看代码的结果。让我们看看Swagger UI,WeatherForecast的测试版本1 API和版本2 的API,看看如果我们发送请求,它们是否正常工作。您可以在下面的截图中看到效果,我们可以选择要检查的API版本:
127185-20220920135542767-246042330.png

我们可以看到v1和v2的WeatherForecast接口是不一样的,v1的版本被抛弃了,所以显示成灰色的。
127185-20220920135617812-1373955920.png
而v2版本是正常的:
127185-20220920135704836-209150655.png
我们可以随便传入一个City参数,然后就可以看到返回记录了:
127185-20220920135715189-615481153.png


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK