How to set up Swashbuckle vs Microsoft.AspNetCore.

2020-05-20 02:23发布

We have asp.net core webapi. We added Microsoft.AspNetCore.Mvc.Versioning and Swashbuckle to have swagger UI. We specified controllers as this:

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ContactController : Controller
{

When we run swagger ui we get version as parameter in routes: enter image description here

How to set-up default "v1" for route ? If version 2 come to the stage how support swagger ui for both versions ?

8条回答
走好不送
2楼-- · 2020-05-20 02:58

@Alezis Nice approach, but if you are using the latest version of Microsoft.AspNetCore.Mvc.Versioning (2.3.0) library, ControllerAttributes() and ActionAttributes() are deprecated, you can update DocInclusionPredicate as follows:

options.DocInclusionPredicate((version, desc) =>
{
    if (!desc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
    var versions = methodInfo.DeclaringType
        .GetCustomAttributes(true)
        .OfType<ApiVersionAttribute>()
        .SelectMany(attr => attr.Versions);
     return versions.Any(v => $"v{v.ToString()}" == version);
});

Swashbuckle.AspNetCore github project helps me a lot.

查看更多
叼着烟拽天下
3楼-- · 2020-05-20 02:58

Instead of tweaking the OpenAPI document, you can use the library provided by Microsoft that adds versions to the API Explorer. That way the versions are provided before Swashbuckle (or another toolchain) needs it and allows you to avoid custom code.

Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer

I was able to get versions configured correctly after adding the package and this block of code.

services.AddVersionedApiExplorer(
    options =>
    {
    // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
    // note: the specified format code will format the version as "'v'major[.minor][-status]"
    options.GroupNameFormat = "'v'VVV";

    // note: this option is only necessary when versioning by url segment. the SubstitutionFormat
    // can also be used to control the format of the API version in route templates
    options.SubstituteApiVersionInUrl = true;
    }
);
查看更多
SAY GOODBYE
4楼-- · 2020-05-20 02:59

If working with .Net Core 3, Basically I have taken @Alezis's solution and updated it to work with .Net core 3:

public void ConfigureServices(IServiceCollection services)
    {
     ....
        services.AddSwaggerGen(options =>
        {
            options.SwaggerDoc("v1", new OpenApiInfo() { Title = "My API", Version = "v1" });
            options.OperationFilter<RemoveVersionFromParameter>();

            options.DocumentFilter<ReplaceVersionWithExactValueInPath>();

        });
      ...
    }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    });
   ...
}

public class RemoveVersionFromParameter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var versionParameter = operation.Parameters.Single(p => p.Name == "version");
        operation.Parameters.Remove(versionParameter);
    }
}

public class ReplaceVersionWithExactValueInPath : 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;
    }
}
查看更多
贼婆χ
5楼-- · 2020-05-20 03:02

@ArlanG it helped me, thanks. It works in Asp.Net Core 3.1. There is one small clarification from my point of view. If you want to get more similar behavior like main answer @Alezis method implementation of DocInclusionPredicate() can be:

options.DocInclusionPredicate((version, desc) =>
            {

                if (!desc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
                var versions = methodInfo.DeclaringType
                    .GetCustomAttributes(true)
                    .OfType<ApiVersionAttribute>()
                    .SelectMany(attr => attr.Versions);


                var maps = methodInfo
                    .GetCustomAttributes(true)
                    .OfType<MapToApiVersionAttribute>()
                    .SelectMany(attr => attr.Versions)
                    .ToArray();

                return versions.Any(v => $"v{v.ToString()}" == version)
                       && (!maps.Any() || maps.Any(v => $"v{v.ToString()}" == version));
            });

In this case when you choose a version on SwaggerUi page, it will show only controller methods that are mapped to this version.

查看更多
做个烂人
6楼-- · 2020-05-20 03:11

I found that using the method ArlanG highlighted took {00:00:00.0001905} to complete whereas running

var versions = methodInfo.DeclaringType.GetConstructors().SelectMany(x =>
    x.DeclaringType.CustomAttributes.Where(y => 
        y.AttributeType == typeof(ApiVersionAttribute))
    .SelectMany(z => z.ConstructorArguments.Select(i=>i.Value)));

took {00:00:00.0000626}

I know we're talking about minor differences but still.

查看更多
狗以群分
7楼-- · 2020-05-20 03:12

In Asp.core 2.+ Add this class:

public class ApiVersionOperationFilter : IOperationFilter
    {
        public void Apply(Operation operation, OperationFilterContext context)
        {
            var actionApiVersionModel = context.ApiDescription.ActionDescriptor?.GetApiVersion();
            if (actionApiVersionModel == null)
            {
                return;
            }

            if (actionApiVersionModel.DeclaredApiVersions.Any())
            {
                operation.Produces = operation.Produces
                    .SelectMany(p => actionApiVersionModel.DeclaredApiVersions
                        .Select(version => $"{p};v={version.ToString()}")).ToList();
            }
            else
            {
                operation.Produces = operation.Produces
                    .SelectMany(p => actionApiVersionModel.ImplementedApiVersions.OrderByDescending(v => v)
                        .Select(version => $"{p};v={version.ToString()}")).ToList();
            }
        }
   }

next add below codes in configureServices method in startup:

services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "Versioned Api v1", Version = "v1" });

                c.OperationFilter<ApiVersionOperationFilter>();
        });

then add below codes in configure method in startup:

            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {                
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Versioned Api v1");
                    c.RoutePrefix = string.Empty;

in Asp.core 3.+ add these classes:

public class RemoveVersionFromParameter : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
                if (!operation.Parameters.Any())
                    return;

                var versionParameter = operation.Parameters
                    .FirstOrDefault(p => p.Name.ToLower() == "version");

                if (versionParameter != null)
                    operation.Parameters.Remove(versionParameter);
        }
    }

 public class ReplaceVersionWithExactValueInPath : IDocumentFilter
    {
        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            if (swaggerDoc == null)
                throw new ArgumentNullException(nameof(swaggerDoc));

            var replacements = new OpenApiPaths();

            foreach (var (key, value) in swaggerDoc.Paths)
            {
                replacements.Add(key.Replace("v{version}", swaggerDoc.Info.Version,
                        StringComparison.InvariantCulture), value);
            }

            swaggerDoc.Paths = replacements;
        }
    }

next add below codes in ConfigureServices method in startup:

protected virtual IEnumerable<int> Versions => new[] {1};

 services.AddSwaggerGen(options =>
            {
                Versions.ToList()
                    .ForEach(v =>
                        options.SwaggerDoc($"v{v}",
                            new OpenApiInfo
                            {
                                Title = $"Versioned Api:v{v}", Version = $"v{v}"
                            }));

                options.OperationFilter<RemoveVersionFromParameter>();
                options.DocumentFilter<ReplaceVersionWithExactValueInPath>();
                options.RoutePrefix = string.Empty;
            });

then add below codes in configure method in startup:

            app.UseSwagger();

            app.UseSwaggerUI(options =>
           {
               Versions.ToList()
                   .ForEach(v => options.SwaggerEndpoint($"/swagger/v{v}/swagger.json", $"Versioned Api:v{v}"));

               options.RoutePrefix = string.Empty;
           });
查看更多
登录 后发表回答