是的,这是可能的。正如您所发现的,Swashbuckle 提供了SwaggerGenOptions.CustomOperationIds() 扩展点。我们可以参与其中。
您需要有一个控制器,其动作用[ApiExplorerSettings] 属性注释。这可确保操作最终出现在正确的 OpenAPI 文档中。
这里我使用Microsoft.AspNetCore.Mvc.Versioning 库进行 API 版本控制。这样我就可以对不同版本对应的不同操作使用相同的路径。但这不是必需的。
[ApiController]
[Route("api/[controller]")]
public class StuffController: ControllerBase
{
[HttpGet("")]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "v1")]
public IActionResult GetStuffTheOldWay()
{
return Ok(nameof(GetStuffTheOldWay));
}
[HttpGet("")]
[ApiVersion("2.0")]
[ApiExplorerSettings(GroupName = "v2")]
public IActionResult GetStuffTheNewWay()
{
return Ok(nameof(GetStuffTheNewWay));
}
}
然后我们可以在构建操作 ID 时使用该组名。
services.AddSwaggerGen(
c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ApiPlayground", Version = "v1" });
c.SwaggerDoc("v2", new OpenApiInfo { Title = "ApiPlayground", Version = "v2" });
c.CustomOperationIds(
description =>
{
if (!(description.ActionDescriptor is ControllerActionDescriptor actionDescriptor))
{
return null; // default behavior
}
return description.GroupName switch
{
"v1" => $"Old{actionDescriptor.ActionName}",
"v2" => $"New{actionDescriptor.ActionName}",
_ => null // default behavior
};
});
}
);
这为我们提供了两个 OpenAPI 文档,它们根据 [ApiVersionAttribute] 组正确地为操作 ID 添加前缀。
// v1 API:
{
"openapi": "3.0.1",
"info": {
"title": "ApiPlayground",
"version": "v1"
},
"paths": {
"/api/Stuff": {
"get": {
"tags": [
"Stuff"
],
"operationId": "OldGetStuffTheOldWay", // <---
// ...
}
// v2 API:
{
"openapi": "3.0.1",
"info": {
"title": "ApiPlayground",
"version": "v2"
},
"paths": {
"/api/Stuff": {
"get": {
"tags": [
"Stuff"
],
"operationId": "NewGetStuffTheNewWay", // <---
// ...
}
一旦您拥有actionDescriptor,您就可以访问 ASP.NET Core 提供的大量元数据供您使用:
在 Swagger UI 中根据版本设置默认参数
Swagger UI 很不错,但它只执行默认 API 版本的操作。我们需要将 API 版本指定为查询参数 api-version=1.0 或在标头中,或作为 URL 的一部分。
为了表达这个要求,我们可以稍微修改一下 OpenAPI 文档并添加一个版本参数,该参数默认为端点对应的任何版本。那就是:
- v1 -> 必须有
?api-version=1.0 参数
- v2 -> 必须有
?api-version=2.0 参数
等等。 Swashbuckle 还有另一个扩展点,operation filters。
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSwaggerGen(
c =>
{
c.SwaggerDoc("1.0", new OpenApiInfo { Title = "ApiPlayground", Version = "v1" });
c.SwaggerDoc("2.0", new OpenApiInfo { Title = "ApiPlayground", Version = "v2" });
c.CustomOperationIds(...);
c.OperationFilter<ApiVersionFilter>(); // <---
}
);
}
private class ApiVersionFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// find all defined versions
var versions = context.ApiDescription
.ActionDescriptor
.EndpointMetadata
.OfType<ApiVersionAttribute>()
.SelectMany(a => a.Versions)
.Select(v => v.ToString()).ToList();
if (!versions.Any())
{
return;
}
// extend openapi schema with a version selector
var firstVersion = versions.First();
var versionEnum = versions.Select(v => new OpenApiString(v)).Cast<IOpenApiAny>().ToList();
operation.Parameters.Add(
new OpenApiParameter
{
In = ParameterLocation.Query,
Name = "api-version",
Description = "The version of the API you want to call",
Example = new OpenApiString(firstVersion),
Schema = new OpenApiSchema
{
Type = "string",
Enum = versionEnum
}
}
);
}
}
完成后,我们会得到一个填充了 api-version 值的 UI。