Reputation: 1028
I'm using swagger to generate my API documentation, and now i needed to version some of my endpoints.
So i configured swagger to identify my versions and map the endpoint correctly. But swagger is lossing its track because i used the same class names on diferent namespaces and i get this error:
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
Here is the example i made to reproduce it
All my swagger packages are in version 6.0.2.
I`m using .Net Core 3.1 WebApi default empty template
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 default configuration classes:
public class SwaggerDefaultValues : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
}
}
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
public void Configure(SwaggerGenOptions options)
{
}
}
A Controller (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 Controller (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; }
}
}
The v1 URL of swagger loads just fine, only when i change it to v2 that this erros shows in the screen:
Fetch error
undefined /swagger/v2/swagger.json
I used the curl generated on the v1 URL to test and everything works just fine, just swagger dont get it
// 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}"
I really dont know if i doing something wrong or swagger really does not support this tipe of versioning.
Any ideas?
Upvotes: 4
Views: 2780
Reputation: 4418
First, the names of types are largely irrelevant. Namespaces are not a concept that can typically be conveyed over HTTP and are highly dependent on the media type. Modern JavaScript does have modules, but it's not quite a namespace. Regardless, a .NET namespace will not be turned into a set of JavaScript modules to be represented in JSON; certainly, not by default.
The systemic issue here seems to be that you've defined a Swashbuckle configuration without any actual configuration. Three things have to line up for all the pieces to come together:
ApiDescription.GroupName
using the formatted API version. The configuration in AddVersionedApiExplorer
helps align that to the typical Swashbuckle formats and examples when versioning by URL segment.UseSwaggerUI
. It is expected to be one endpoint per API version. The segment value typically maps to the API version group name (e.g. formatted version)ApiDescription.GroupName
and the segment of the endpoint defined in #2.You have not provided any implementation or configuration for that in #3. As a result, Swashbuckle assumes there is only a single document. Since you have models with the same name and in the same document, you get a document generation error. You cannot have duplicate route paths or model names in the same document. You can, however, have duplicate model names across multiple versions when you define a document per API version.
Side Note: It is possible to have duplicate .NET type names within the same API version, but you will have to provide an alias. Namespace is never considered for distinctness. Unless it really doesn't make sense, I would recommend you use unique types/classes for model names within each set of versioned APIs.
The ASP.NET API Versioning Swagger Sample demonstrates what the configuration should look like in totality. A typical configuration will look something like:
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(),
} );
}
}
Upvotes: 1