asp.net core swashbuckle 如何在同一控制器中使用版本控制
asp.net core swashbuckle how to use versioning in same controller
当在 asp.net 内核中使用带有 swashbuckle 的版本控制时,默认情况下您不能在同一个控制器中使用多个方法,例如:
[Route("v{version:apiVersion}/[controller]")]
[ApiVersion("1")]
[ApiVersion("2")]
[ApiController]
public class TestController : ControllerBase
{
/// <summary>
/// test ver 1
/// </summary>
/// <returns></returns>
[HttpGet]
[MapToApiVersion("1")]
[ProducesResponseType(StatusCodes.Status200OK)]
public string Get()
{
return "Version 1";
}
/// <summary>
/// test ver 2
/// </summary>
/// <returns></returns>
[HttpGet]
[MapToApiVersion("2")]
[ProducesResponseType(StatusCodes.Status200OK)]
public string Get2()
{
return "Version 2";
}
}
默认情况下,您会收到错误消息
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException:
Conflicting method/path combination "GET v{version}/Test" for actions
- ...Controllers.TestController.Get,...TestController.Get2
因为 swagger-ui 通过相同的路径识别了两种方法
localhost/v{version:apiVersion}/Test
基本的 swashbuckle 解决方案是为每个版本的方法设置两个单独的控制器。这对我的用例来说是不好的解决方案。
解决方案是在 swagger 中手动检查版本 setter。就我而言,我还添加了支持的版本列表。
所以你必须添加到 Startup.cs 这个:
private readonly string[] supportedVersions = new string[] { "1", "2" };
public void ConfigureServices(IServiceCollection services)
{
...
services.AddApiVersioning();
services.AddSwaggerGen(c =>
{
c.DocInclusionPredicate((docName, apiDesc) =>
{
var metadata = apiDesc.ActionDescriptor.EndpointMetadata;
var apiVersionModel = metadata.Where(x => x.GetType() == typeof(ApiVersionAttribute)).Cast<ApiVersionAttribute>();
var supportedVersions = metadata.Where(x => x.GetType() == typeof(MapToApiVersionAttribute)).Cast<MapToApiVersionAttribute>();
if (apiVersionModel.IsNullOrEmpty() || supportedVersions.IsNullOrEmpty())
{
return false;
}
var apiVersions = apiVersionModel.SelectMany(x => x.Versions.Select(y => y.MajorVersion));
var versions = supportedVersions.SelectMany(x => x.Versions.Select(y => y.MajorVersion));
return apiVersions.Any(v => $"v{v.ToString()}" == docName) && versions.Any(v => $"v{v.ToString()}" == docName);
});
c.DocumentFilter<ReplaceVersionWithExactValueInPathFilter>();
c.OperationFilter<RemoveVersionParameterFilter>();
c.EnableAnnotations();
supportedVersions.ForEach(sv =>
{
c.SwaggerDoc($"v{sv}", new OpenApiInfo()
{
Version = $"v{sv}",
Title = $"API v{ sv }",
Description = "My web api",
});
});
var baseDirectory = AppContext.BaseDirectory;
var xmlFiles = Directory.EnumerateFiles(baseDirectory, "*.xml");
xmlFiles.ForEach(x => c.IncludeXmlComments(x));
});
...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwaggerUI(c =>
{
supportedVersions.ForEach(sv =>
{
c.SwaggerEndpoint($"/swagger/v{sv}/swagger.json", $"My api v{sv}");
});
});
}
并分隔文件两种方法以避免在 UI 中将版本显示为参数。否则,您将看到带有版本参数而不是版本本身的端点。
public class RemoveVersionParameterFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var versionParameter = operation.Parameters.FirstOrDefault(p => p.Name == "version");
if (versionParameter == default(OpenApiParameter)) return;
var parameters = operation.Parameters.ToList();
operation.Parameters.Remove(versionParameter);
}
}
public class ReplaceVersionWithExactValueInPathFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var paths = new OpenApiPaths();
foreach (var path in swaggerDoc.Paths)
{
paths.Add(path.Key.Replace("v{version}", swaggerDoc.Info.Version), path.Value);
}
swaggerDoc.Paths = paths;
}
}
当在 asp.net 内核中使用带有 swashbuckle 的版本控制时,默认情况下您不能在同一个控制器中使用多个方法,例如:
[Route("v{version:apiVersion}/[controller]")]
[ApiVersion("1")]
[ApiVersion("2")]
[ApiController]
public class TestController : ControllerBase
{
/// <summary>
/// test ver 1
/// </summary>
/// <returns></returns>
[HttpGet]
[MapToApiVersion("1")]
[ProducesResponseType(StatusCodes.Status200OK)]
public string Get()
{
return "Version 1";
}
/// <summary>
/// test ver 2
/// </summary>
/// <returns></returns>
[HttpGet]
[MapToApiVersion("2")]
[ProducesResponseType(StatusCodes.Status200OK)]
public string Get2()
{
return "Version 2";
}
}
默认情况下,您会收到错误消息
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Conflicting method/path combination "GET v{version}/Test" for actions
- ...Controllers.TestController.Get,...TestController.Get2
因为 swagger-ui 通过相同的路径识别了两种方法
localhost/v{version:apiVersion}/Test
基本的 swashbuckle 解决方案是为每个版本的方法设置两个单独的控制器。这对我的用例来说是不好的解决方案。
解决方案是在 swagger 中手动检查版本 setter。就我而言,我还添加了支持的版本列表。
所以你必须添加到 Startup.cs 这个:
private readonly string[] supportedVersions = new string[] { "1", "2" };
public void ConfigureServices(IServiceCollection services)
{
...
services.AddApiVersioning();
services.AddSwaggerGen(c =>
{
c.DocInclusionPredicate((docName, apiDesc) =>
{
var metadata = apiDesc.ActionDescriptor.EndpointMetadata;
var apiVersionModel = metadata.Where(x => x.GetType() == typeof(ApiVersionAttribute)).Cast<ApiVersionAttribute>();
var supportedVersions = metadata.Where(x => x.GetType() == typeof(MapToApiVersionAttribute)).Cast<MapToApiVersionAttribute>();
if (apiVersionModel.IsNullOrEmpty() || supportedVersions.IsNullOrEmpty())
{
return false;
}
var apiVersions = apiVersionModel.SelectMany(x => x.Versions.Select(y => y.MajorVersion));
var versions = supportedVersions.SelectMany(x => x.Versions.Select(y => y.MajorVersion));
return apiVersions.Any(v => $"v{v.ToString()}" == docName) && versions.Any(v => $"v{v.ToString()}" == docName);
});
c.DocumentFilter<ReplaceVersionWithExactValueInPathFilter>();
c.OperationFilter<RemoveVersionParameterFilter>();
c.EnableAnnotations();
supportedVersions.ForEach(sv =>
{
c.SwaggerDoc($"v{sv}", new OpenApiInfo()
{
Version = $"v{sv}",
Title = $"API v{ sv }",
Description = "My web api",
});
});
var baseDirectory = AppContext.BaseDirectory;
var xmlFiles = Directory.EnumerateFiles(baseDirectory, "*.xml");
xmlFiles.ForEach(x => c.IncludeXmlComments(x));
});
...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwaggerUI(c =>
{
supportedVersions.ForEach(sv =>
{
c.SwaggerEndpoint($"/swagger/v{sv}/swagger.json", $"My api v{sv}");
});
});
}
并分隔文件两种方法以避免在 UI 中将版本显示为参数。否则,您将看到带有版本参数而不是版本本身的端点。
public class RemoveVersionParameterFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var versionParameter = operation.Parameters.FirstOrDefault(p => p.Name == "version");
if (versionParameter == default(OpenApiParameter)) return;
var parameters = operation.Parameters.ToList();
operation.Parameters.Remove(versionParameter);
}
}
public class ReplaceVersionWithExactValueInPathFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var paths = new OpenApiPaths();
foreach (var path in swaggerDoc.Paths)
{
paths.Add(path.Key.Replace("v{version}", swaggerDoc.Info.Version), path.Value);
}
swaggerDoc.Paths = paths;
}
}