0

I'm working on a public facing API, using Swashbuckle.AspNetCore & ReDoc for documentation with Microsoft.AspNetCore.Mvc.Versioning for versioning our controllers using attributes.

We want to have a swagger document that shows all of the latest versions of our endpoints to make it easier for people coming to the API for the first time to pick the correct version.

My current attempt was to create a 'v0' and apply that version to all of the latest versions of the controllers. I then used an operation filter to replace 'v0' with whatever the latest version was:

public class LatestVersionOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var version = (context.MethodInfo.GetCustomAttributes<ApiVersionAttribute>()
                .Union(context.MethodInfo.DeclaringType.GetCustomAttributes<ApiVersionAttribute>())
                .SelectMany(x => x.Versions)
                .OrderByDescending(x => x.MajorVersion))
                .FirstOrDefault(x => x.MajorVersion != 0);
        if (version != null && context.ApiDescription.RelativePath.Contains("v0"))
        {
            context.ApiDescription.RelativePath = context.ApiDescription.RelativePath
                .Replace("v0", $"v{version.MajorVersion}");
        }
    }
}

This works most of the time, but sometimes it doesn't seem to kick in, and you end up with a bunch of URLs with 'v0' in. The filter runs, but doesn't seem to be reflected in the resulting swagger document.

Is there some better way to achieve what we are aiming for here? I attempted to write something using DocInclusionPredicate but I couldn't seem to get what I wanted.

4

1 回答 1

0

After some more digging around, I found this old issue: https://github.com/domaindrivendev/Swashbuckle/issues/361

I'd tried a document filter before, but it felt like the wrong path to go down. Seeing this issue, I had another go and the below is now working for us:

public class LatestVersionDocumentFilter : IDocumentFilter
{
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        foreach (var path in swaggerDoc.Paths.ToList())
        {
            if (!path.Key.Contains("v0"))
            {
                continue;
            }

            var apiDescription = context.ApiDescriptions.FirstOrDefault(x => x.RelativePath == path.Key.TrimStart('/'));
            if (apiDescription != null && apiDescription.TryGetMethodInfo(out var methodInfo))
            {
                var latestVersion = methodInfo.GetCustomAttributes<ApiVersionAttribute>()
                    .Union(methodInfo.DeclaringType.GetCustomAttributes<ApiVersionAttribute>())
                    .SelectMany(x => x.Versions)
                    .OrderByDescending(x => x.MajorVersion)
                    .FirstOrDefault(x => x.MajorVersion != 0);

                if (latestVersion != null)
                {
                    swaggerDoc.Paths.Remove(path.Key);
                    var latestUrl = path.Key.Replace("/v0/", $"/v{latestVersion.MajorVersion}/");
                    swaggerDoc.Paths.Add(latestUrl, path.Value);
                }
            }
        }
    }
}
于 2020-03-13T11:11:13.527 回答