Swagger:我需要版本控制和分组

问题描述 投票:0回答:2

使用 .Net 5 REST API 大摇大摆。

我有一个包含数百种方法的 API,需要将相关 API 组合在一起使用。同时,我需要开始开发我的 API 的 V2 版本。我花了几个小时尝试不同的方法来实现版本控制和分组,但没有成功。

我有几个控制器,一些用于V1,一些用于V2。有人可以提供一个完整的例子吗?

asp.net-core swagger grouping api-versioning
2个回答
1
投票

在进入解决方案之前,让我确定几点:

  • 上面的 YouTube 视频和@abdusco 在他们看来提供了他们自己的 API 版本控制 方法,但问题是关于 ASP.NET Core API 版本控制
  • 虽然没有具体说明,但我假设您没有按 URL 段进行版本控制
  • 默认的 OpenAPI/Swagger UI 只有一个轴心点(例如组),您可以在下拉列表中选择它
  • OpenAPI/Swagger 文档必须 具有唯一的 URL,这意味着您不能在同一个文档中拥有多个 API,除非您按 URL 段进行版本控制(最差的选择;最不符合 REST 风格)
  • By Swagger,我想你的意思是 Swashbuckle。 Swashbuckle OpenAPI 生成器使用
    ApiDescription.GroupName
    在每个组的单个文档中整理 API,映射到 UI 中的下拉列表。改变这种行为是可能的,但并非微不足道。
  • API 版本控制的 API Explorer 扩展的默认行为将
    ApiDescription.GroupName
    设置为格式化的 API 版本值,如果没有另外设置,以便 API 按版本分组。在
    4.1
    之前,
    [ApiExplorerSettings(GroupName="...")]
    被忽略并被覆盖。

归根结底,您需要两个分组维度:categoryAPI version。不幸的是,API Explorer、OpenAPI/Swagger UI 和 Swashbuckle 仅在本质上支持其中一种。开箱即用不支持任何形式的层次结构。有几种方法可以解决这个问题:

  1. 连接
    group name
    API version
    作为一个维度
  2. 创建一个 Swashbuckle 扩展(例如:IOperationFilter)为特定组添加 OpenAPI 标签。然后可以更改 UI 以根据这些标签添加第二个下拉列表以进行过滤,或者标签可用于整理组(想想 Expander 控件)。
  3. 按 URL 段版本,生成单个 OpenAPI 文档,将
    GroupName
    更新回您想要的方式(这种方法推荐)

可能更多的选择,但这些是最明智的。虽然它 可能不是您所希望的,但考虑到工作的限制,

#3
是最直接的实施方式。它看起来像这样:

public class SubgroupDescriptionProvider : IApiDescriptionProvider
{
    private readonly IOptions<ApiExplorerOptions> options;

    public SubgroupDescriptionProvider(IOptions<ApiExplorerOptions> options)
        => this.options = options;

    // Execute after DefaultApiVersionDescriptionProvider.OnProvidersExecuted
    public int Order => -1;

    public void OnProvidersExecuting(ApiDescriptionProviderContext context) { }

    public void OnProvidersExecuted(ApiDescriptionProviderContext context)
    {
        var format = options.Value.GroupNameFormat;
        var culture = CultureInfo.CurrentCulture;
        var results = context.Results;
        var newResults = new List<ApiDescription>(capacity: results.Count);

        for (var i = 0; i < results.Count; i++)
        {
            var result = results[i];
            var apiVersion = result.GetApiVersion();
            var versionGroupName = apiVersion.ToString(format, culture);

            // [ApiExplorerSettings(GroupName="...")] was NOT set so
            // nothing else to do
            if (result.GroupName == versionGroupName)
            {
                continue;
            }

            // must be using [ApiExplorerSettings(GroupName="...")] so
            // concatenate it with the formatted API version
            result.GroupName += " " + versionGroupName;

            // optional: add version grouping as well
            // note: this works because the api description will appear in
            // multiple, but different, documents
            var newResult = result.Clone();

            newResult.GroupName = versionGroupName;
            newResults.Add(newResult);
        }

        newResults.ForEach(results.Add);
    }
}

然后您使用 DI 注册它:

services.TryAddEnumerable(
    ServiceDescriptor.Transient<IApiDescriptionProvider, SubgroupDescriptionProvider>());

如果你同时使用两组分组,那么你将得到多个 OpenAPI 文档。

按 API 版本(默认)

├─ v1
│   ├─ /utility
│   └─ /not-utility
└─ v2
    ├─ /utility
    └─ /not-utility

按组名+API版本

├─ Utilities v1   
│   └─ /utility
├─ Not Utilities v1
│   └─ /not-utility
├─ Utilities v2
│   └─ /utility
└─ Not Utilities v2
    └─ /not-utility

如果您的版本控制策略类似于

N-2
,那么列表应该是合理的。是
group * version
的产物,所以这个榜单就看你最终有多少组和多少版本了

拥有一个真正的二维层次结构应该在技术上是可行的,但我从来没有做过,也不知道有人愿意付出努力来实现它。该解决方案将非常具体您的 API 集。


0
投票

在 program.cs (.net 6) 中使用以下代码,如果您使用的是 .net 5(或以下版本),请在 strartup.cs 中使用此代码

        services.AddVersionedApiExplorer(options =>
          {
            options.GroupNameFormat = "'v'VV";
            options.SubstituteApiVersionInUrl = true;
          });

         services.AddSwaggerGen(options =>
          {

            options.DocInclusionPredicate((documentName, apiDescription) =>
            {
                var actionApiVersionModel = apiDescription.ActionDescriptor.GetApiVersionModel();
                var apiExplorerSettingsAttribute = (ApiExplorerSettingsAttribute)apiDescription.ActionDescriptor.EndpointMetadata.First(x => x.GetType().Equals(typeof(ApiExplorerSettingsAttribute)));
                if (actionApiVersionModel == null)
                {
                    return true;
                }
                if (actionApiVersionModel.DeclaredApiVersions.Any())
                {
                    return actionApiVersionModel.DeclaredApiVersions.Any(v =>
                    $"{apiExplorerSettingsAttribute.GroupName}v{v}" == documentName);
                }
                return actionApiVersionModel.ImplementedApiVersions.Any(v =>
                       $"{apiExplorerSettingsAttribute.GroupName}v{v}" == documentName);
            });

            var apiVersionDescriptionProvider = services.BuildServiceProvider().GetService<IApiVersionDescriptionProvider>();
            foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
            {
                options.SwaggerDoc($"Orders{description.GroupName}", new OpenApiInfo
                {
                    Title = "Example Service",
                    Description = "Example Service -- Backend Service Project",
                    Version = description.ApiVersion.ToString(),
                    
                });
                options.SwaggerDoc($"Payments{description.GroupName}", new OpenApiInfo
                {
                    Title = "Example Service",
                    Description = "Example Service -- Backend Service Project",
                    Version = description.ApiVersion.ToString(),
                });
                // Orders & Payments are the groupNames, above ex: out put will be : swagger/OrdersV1.0/swagger.json ( if you select Orders from the swagger definition dropdown) 
                // If you select Payments => output : swagger/PaymentsV1.0/swagger.json
            }
            
            
//var app = builder.Build();
                
            app.UseSwagger();

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

控制器 //V1 // 示例控制器 1:

[ApiVersion("1.0")]
[ApiController]
[ApiExplorerSettings(GroupName = "Orders")]
[Route("api/v{version:apiVersion}/[controller]")]
public class OrdersController : ControllerBase              
            

// 示例控制器 2:

[ApiVersion("1.0")]
[ApiController]
[ApiExplorerSettings(GroupName = "Payments")]
[Route("api/v{version:apiVersion}/[controller]")]
public class PaymentsController : ControllerBase    
© www.soinside.com 2019 - 2024. All rights reserved.