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;
    }
}