jez*_*pin 8 swagger swagger-ui api-versioning asp.net-core
我的解决方案中有以下控制器设置:
[Route("api/v{VersionId}/[controller]")]
[ApiController]
[Produces("application/json")]
[Consumes("application/json")]
public class MyBaseController : ControllerBase
{
}
[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class AuthenticationController : MyBaseController
{
private readonly ILoginService _loginService;
public AuthenticationController(ILoginService loginService)
{
_loginService = loginService;
}
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[HttpPost("login")]
public ActionResult<v1.JwtTokenResponse> Login([FromBody] v1.LoginRequest loginRequest)
{
var loginResult = _loginService.Login(loginRequest.Email, loginRequest.Password);
if (loginResult.StatusCode != HttpStatusCode.OK)
{
return StatusCode((int)loginResult.StatusCode);
}
var tokenResponse = new v1.JwtTokenResponse() { Token = loginResult.Token };
return Ok(tokenResponse);
}
}
Run Code Online (Sandbox Code Playgroud)
在我的 API 的两个版本之间,此方法没有任何变化,因此从逻辑上讲,我想在我的文档中显示新版本仍然支持该方法。假设我们有第二个客户控制器,它的逻辑发生了一些变化,因此这就是我们拥有新版本 1.1 的原因,因为语义版本控制指示已添加新内容,但以向后兼容的方式添加。
运行这段代码时,自然一切都会构建得很好。代码是有效的,.net core 允许这种实现,但是,当涉及到 swagger gen 时,我遇到了问题,并产生以下错误:
NotSupportedException: Conflicting method/path combination "POST api/v{VersionId}/Authentication/login" for actions - Template.Api.Endpoints.Controllers.AuthenticationController.Login (Template.Api.Endpoints),Template.Api.Endpoints.Controllers.AuthenticationController.Login (Template.Api.Endpoints). Actions require a unique method/path combination for Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround
Run Code Online (Sandbox Code Playgroud)
正如您在上面所看到的,路径是不同的,因为传递到路由中的版本参数使其如此。此外,纯粹创建一个全新的方法来表示代码可通过文档获得是没有意义的,所以我的问题是为什么 swagger 会忽略路径中的版本差异并建议 ConflictingActionsResolver 的用户?
此外,在进一步深入研究并发现很多其他人也遇到同样的问题之后(标头版本控制是社区的一个特定问题,而 Swaggers 的强硬方法与此相冲突),一般方法似乎是使用冲突的操作解析器仅获取它遇到的第一个描述,这只会在 api 文档中公开版本 1.0,而忽略 1.1 版本,从而在 Swagger 中给人留下没有可用端点的 1.1 版本的印象。
NotSupportedException: Conflicting method/path combination "POST api/v{VersionId}/Authentication/login" for actions - Template.Api.Endpoints.Controllers.AuthenticationController.Login (Template.Api.Endpoints),Template.Api.Endpoints.Controllers.AuthenticationController.Login (Template.Api.Endpoints). Actions require a unique method/path combination for Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround
Run Code Online (Sandbox Code Playgroud)
我们如何解决这个问题并在 Swagger 中正确显示可用端点,而不必创建新方法来有效地导致代码重复,只是为了满足 Swagger 规范中看似疏忽的情况?任何帮助将不胜感激。
注意:许多人可能建议在路由末尾附加操作,但我们希望避免这种情况,因为这意味着我们的端点在我们想要争取诸如具有派生 CRUD 的 GET、POST、PUT 属性的客户/1 之类的内容时不稳定操作,而无需附加诸如customers/add_customer_1或customers/add_customer_2之类的内容来反映URL中的方法名称。
这是我使用时的 Swagger 设置HeaderApiVersionReader。
public class SwaggerOptions
{
public string Title { get; set; }
public string JsonRoute { get; set; }
public string Description { get; set; }
public List<Version> Versions { get; set; }
public class Version
{
public string Name { get; set; }
public string UiEndpoint { get; set; }
}
}
Run Code Online (Sandbox Code Playgroud)
在启动#ConfigureServices中
services.AddApiVersioning(apiVersioningOptions =>
{
apiVersioningOptions.AssumeDefaultVersionWhenUnspecified = true;
apiVersioningOptions.DefaultApiVersion = new ApiVersion(1, 0);
apiVersioningOptions.ReportApiVersions = true;
apiVersioningOptions.ApiVersionReader = new HeaderApiVersionReader("api-version");
});
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(swaggerGenOptions =>
{
var swaggerOptions = new SwaggerOptions();
Configuration.GetSection("Swagger").Bind(swaggerOptions);
foreach (var currentVersion in swaggerOptions.Versions)
{
swaggerGenOptions.SwaggerDoc(currentVersion.Name, new OpenApiInfo
{
Title = swaggerOptions.Title,
Version = currentVersion.Name,
Description = swaggerOptions.Description
});
}
swaggerGenOptions.DocInclusionPredicate((version, desc) =>
{
if (!desc.TryGetMethodInfo(out MethodInfo methodInfo))
{
return false;
}
var versions = methodInfo.DeclaringType.GetConstructors()
.SelectMany(constructorInfo => constructorInfo.DeclaringType.CustomAttributes
.Where(attributeData => attributeData.AttributeType == typeof(ApiVersionAttribute))
.SelectMany(attributeData => attributeData.ConstructorArguments
.Select(attributeTypedArgument => attributeTypedArgument.Value)));
return versions.Any(v => $"{v}" == version);
});
swaggerGenOptions.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"));
//... some filter settings here
});
Run Code Online (Sandbox Code Playgroud)
在启动#配置中
var swaggerOptions = new SwaggerOptions();
Configuration.GetSection("Swagger").Bind(swaggerOptions);
app.UseSwagger(option => option.RouteTemplate = swaggerOptions.JsonRoute);
app.UseSwaggerUI(option =>
{
foreach (var currentVersion in swaggerOptions.Versions)
{
option.SwaggerEndpoint(currentVersion.UiEndpoint, $"{swaggerOptions.Title} {currentVersion.Name}");
}
});
Run Code Online (Sandbox Code Playgroud)
应用程序设置.json
{
"Swagger": {
"Title": "App title",
"JsonRoute": "swagger/{documentName}/swagger.json",
"Description": "Some text",
"Versions": [
{
"Name": "2.0",
"UiEndpoint": "/swagger/2.0/swagger.json"
},
{
"Name": "1.0",
"UiEndpoint": "/swagger/1.0/swagger.json"
}
]
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
11954 次 |
| 最近记录: |