C# .Net Core 3.1 swagger API版本控制冲突名称空间url

C# .Net Core 3.1 swagger API版本控制冲突名称空间url,c#,.net-core,swagger,swagger-ui,api-versioning,C#,.net Core,Swagger,Swagger Ui,Api Versioning,我正在使用swagger生成API文档,现在我需要对一些端点进行版本设置 所以我配置了swagger来识别我的版本并正确映射端点。但是,由于我在不同的名称空间中使用了相同的类名,所以swagger正在失去它的踪迹,我得到了以下错误: Conflicting method/path combination "GET api/v1/A" for actions - TesteSwagger.Controllers.B.AController.x (TesteSwagger),Te

我正在使用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
这是我复制的例子

我所有的招摇过市软件包都是6.0.2版

我使用的是.NETCore3.1WebAPI默认的空模板

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();
            });
        }
    public class SwaggerDefaultValues : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            
        }
    }

    public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
    {
        public void Configure(SwaggerGenOptions options)
        {
        }
    }
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; }
    }
}
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; }
    }
}
B控制器(v2):

        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();
            });
        }
    public class SwaggerDefaultValues : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            
        }
    }

    public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
    {
        public void Configure(SwaggerGenOptions options)
        {
        }
    }
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; }
    }
}
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
我使用v1url上生成的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名称空间不会转换为一组JavaScript模块,以JSON表示;当然,不是默认的

这里的系统性问题似乎是您定义了一个虚张声势的配置,而没有任何实际的配置。三件事必须排成一行,才能让所有的部分走到一起:

  • 添加版本化的API Explorer-提供API版本化扩展,使用格式化的API版本将
    apiscription.GroupName
    整理成组。当按URL段进行版本控制时,
    AddVersionedApiExplorer
    中的配置有助于将其与典型的Swashback格式和示例对齐
  • 添加招摇过市端点-通过
    使用招摇过市界面
    。每个API版本预期有一个端点。段值通常映射到API版本组名称(例如格式化版本)
  • 添加虚张声势文档-配置虚张声势以定义每个API版本的开放API(以前称为虚张声势)文档。文档的键映射到
    apiscription.GroupName
    和#2中定义的端点段
  • 您尚未在#3中提供任何实现或配置。因此,Swashback假定只有一个文档。由于同一文档中的模型具有相同的名称,因此会出现文档生成错误。同一文档中不能有重复的管线路径或模型名称。但是,在为每个API版本定义文档时,可以跨多个版本使用重复的模型名称

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

    ASP.NET API版本控制演示了配置的总体外观。典型配置如下所示:

    public void配置(招摇过市选项)
    {
    //配置Swashback以根据定义的API版本定义一个Swagger文档
    foreach(provider.ApiVersionDescriptions中的变量描述)
    {
    options.SwaggerDoc(
    description.GroupName,
    新openapinfo()
    {
    Title=“API”+description.GroupName,
    Version=description.apivision.ToString(),
    } );
    }
    }
    
    我认为您有一个名称空间TesteSwagger.Controllers.B和一个令人困惑的类B。错误消息说,我没有使用ProducesResponseType(typeof(B))try ProducesResponseType(typeof(TesteSwagger.Controllers.B.B))不工作,而是完全限定了两个控制器中的参数和响应,仍然存在相同的问题。错误消息指出需要使用全名(包括命名空间)。尚不清楚需要更改哪些对象。我只是猜是B班。Th3可能还有其他类似的对象也需要更改。没有其他对象,这是一个仅为模拟错误而创建的干净项目,这是我的解决方案中仅有的两个对象和唯一的控制器您是否尝试过“x(a a)=>Ok(new B())”的全名?我不知道swagger是如何将字符串名转换为类类型的,但很明显,它不喜欢以句点后跟“B”的名称空间。