.Net Core 3.1 swagger API 版本冲突命名空间 url

问题描述

我正在使用 swagger 来生成我的 API 文档,现在我需要对我的一些端点进行版本控制。

所以我配置了swagger来识别我的版本并正确映射端点。但是 swagger 正在失去它的踪迹,因为我在不同的命名空间上使用了相同的类名,并且我收到了这个错误

Conflicting method/path combination "GET api/v1/A" for actions - TesteSwagger.Controllers.B.AController.x (TesteSwagger),TesteSwagger.Controllers.A.AController.x (TesteSwagger). Actions require a unique method/path combination for Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround

这是我复制它的例子

我所有的 swagger 包都是 6.0.2 版。

我使用的是 .Net Core 3.1 WebApi 认空模板

Startup.cs:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

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

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

            services.AddTransient<IConfigureOptions<SwaggerGenoptions>,ConfigureSwaggerOptions>();

            services.AddSwaggerGen(options =>
            {
                options.CustomSchemaIds(x => x.FullName);
                options.DescribeAllParametersInCamelCase();
                options.OperationFilter<SwaggerDefaultValues>();
                options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
            });
        }

        public void Configure(IApplicationBuilder app,IWebHostEnvironment env,IApiVersionDescriptionProvider provider)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

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

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

Swagger 认配置类:

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

    public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenoptions>
    {
        public void Configure(SwaggerGenoptions options)
        {
        }
    }

控制器 (v1):

namespace TesteSwagger.Controllers.A
{
    [ApiController,ApiVersion("1.0")]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class AController : ControllerBase
    {
        [HttpPost]
        [ProducesResponseType(typeof(B),(int)HttpStatusCode.OK)]
        public IActionResult x(A a) => Ok(new B());
    }

    public class A
    {
        public int Foo { get; set; }
    }

    public class B
    {
        public int Bar { get; set; }
    }
}

B 控制器 (v2):

namespace TesteSwagger.Controllers.B
{
    [ApiController,ApiVersion("2.0")]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class AController : ControllerBase
    {
        [HttpPost]
        [ProducesResponseType(typeof(B),(int)HttpStatusCode.OK)]
        public IActionResult x(A a) => Ok(new B());
    }

    public class A
    {
        public int Foo { get; set; }
    }

    public class B
    {
        public int Bar { get; set; }
    }
}

swagger 的 v1 URL 加载得很好,只有当我将其更改为 v2 时才会在屏幕上显示错误

Fetch error
undefined /swagger/v2/swagger.json

我使用 v1 URL 上生成的 curl 进行测试,一切正常,只是大摇大摆不明白

// works great
curl -X POST "https://localhost:44312/api/v1/A" -H  "accept: text/plain" -H  "Content-Type: application/json" -d "{\"foo\":0}"

// works great
curl -X POST "https://localhost:44312/api/v2/A" -H  "accept: text/plain" -H  "Content-Type: application/json" -d "{\"foo\":0}"

我真的不知道是我做错了什么还是招摇真的不支持这种版本控制。

有什么想法吗?

解决方法

首先,类型的名称在很大程度上是无关紧要的。命名空间不是一个通常可以通过 HTTP 传达的概念,它高度依赖于媒体类型。现代 JavaScript 确实有模块,但它不是一个命名空间。无论如何,.NET 命名空间不会变成一组用 JSON 表示的 JavaScript 模块;当然,不是默认。

这里的系统性问题似乎是您在没有任何实际配置的情况下定义了 Swashbuckle 配置。必须将三件事情排列在一起才能将所有部分组合在一起:

  1. 添加版本化 API 资源管理器 - 提供 API 版本化扩展,这些扩展使用格式化的 API 版本将 ApiDescription.GroupName 整理成组。 AddVersionedApiExplorer 中的配置有助于在按 URL 段进行版本控制时将其与典型的 Swashbuckle 格式和示例保持一致。
  2. 添加 Swagger 端点 - 通过 UseSwaggerUI。预计每个 API 版本是一个端点。段值通常映射到 API 版本组名称(例如格式化版本)
  3. 添加 Swagger 文档 - 配置 Swashbuckle 以定义每个 API 版本的 Open API(以前称为 Swagger)文档。文档的键映射到 ApiDescription.GroupName 和 #2 中定义的端点的段。

您没有在 #3 中提供任何实现或配置。因此,Swashbuckle 假定只有一个文档。由于您在同一个文档中有具有相同名称的模型,因此您会收到文档生成错误。同一文档中不能有重复的路径路径或模型名称。但是,当您为每个 API 版本定义一个文档时,可以在多个版本中具有重复的模型名称。

旁注:在同一 API 版本中可能有重复的 .NET 类型名称,但您必须提供别名。命名空间从不考虑独特性。除非它真的没有意义,否则我建议您在每组版本化 API 中为模型名称使用唯一的类型/类。

ASP.NET API 版本控制 Swagger Sample 演示了配置的总体情况。典型的配置如下所示:

public void Configure( SwaggerGenOptions options )
{
    // configure Swashbuckle to define a Swagger document per defined API version
    foreach ( var description in provider.ApiVersionDescriptions )
    {
        options.SwaggerDoc(
            description.GroupName,new OpenApiInfo()
            {
                Title = "API " + description.GroupName,Version = description.ApiVersion.ToString(),} );
    }
}