Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swagger not finding ApiVersion-ed actions

I'm building a Web API in ASP.NET Core and am experimenting with multiple versions. I am creating Swagger docs for each version following these instructions: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/README.md#customize-the-action-selection-process

I have 2 versions of the same action defined like this:

namespace Web.Api
{
    [ApiController]
    [Area("api")]
    [Route("[area]/[controller]")]
    public abstract class MyApiControllerBase : ControllerBase
    {

    }
}

namespace Web.Api.V1
{
    public class TestController : MyApiControllerBase
    {
        [HttpGet]
        [ApiVersion("1.0")]
        public IActionResult Get()
        {
            return Ok("Version 1.0 endpoint.");
        }
    }
}

namespace Web.Api.V2
{
    public class TestController : MyApiControllerBase
    {
        [HttpGet]
        [ApiVersion("2.0")]
        public IActionResult Get()
        {
            return Ok("Version 2.0 endpoint.");
        }
    }
}

And my Swagger Gen configured like this in Startup.cs:

services.AddSwaggerGen(options =>
{
    options.DocInclusionPredicate((docName, apiDesc) =>
    {
        if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo))
        {
            return false;
        }

        IEnumerable<ApiVersion> versions = methodInfo.DeclaringType
            .GetCustomAttributes(true)
            .OfType<ApiVersionAttribute>()
            .SelectMany(a => a.Versions);

        return versions.Any(v => $"v{v.ToString()}" == docName);
    });

    options.SwaggerDoc("v1.0", new OpenApiInfo { Title = "My API", Version = "v1.0" });
    options.SwaggerDoc("v2.0", new OpenApiInfo { Title = "My API", Version = "v2.0" });
});

However when I visit my Swagger UI it says "No operations defined in spec!" for both v1.0 and v2.0. If I inspect the DocInclusionPredicate when loading the Swagger doc it appears that the ApiVersion Attribute isn't being picked up and thus explains why they aren't included in the docs. For example, when Web.Api.V2.TestController.Get is passed as the apiDesc argument, this is what is found for Attributes:

Attributes

Only ApiControllerAttribute, AreaAttribute, RouteAttribute, and ControllerAttribute are found. All of these are Attributes on base classes, not directly on the TestController or Get() action itself.

like image 499
Osirus Avatar asked Nov 06 '22 10:11

Osirus


1 Answers

For asp.net core 3.0,you could follow the following steps:

1.Install Swashbuckle.AspNetCore 5.0.0

2.Install Microsoft.AspNetCore.Mvc.Versioning 4.0.0

3.Install Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer 4.0.0

4.Change your controller like below(ApiVersion needs to be declared on controller):

[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
public class TestController : MyApiControllerBase
{
    [HttpGet]      
    public IActionResult Get()
    {
        return Ok("Version 1.0 endpoint.");
    }
}
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("2.0")]
public class TestController : MyApiControllerBase
{
    [HttpGet]        
    public IActionResult Get()
    {
        return Ok("Version 2.0 endpoint.");
    }
}

5.Startup.cs(Note that you need to use services.AddVersionedApiExplorer and set options.SubstituteApiVersionInUrl=true to tell swagger to replace the version in the controller route and configure the api version: ):

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddApiVersioning(o => {
        o.ReportApiVersions = true;
        o.AssumeDefaultVersionWhenUnspecified = true;
        o.DefaultApiVersion = new ApiVersion(1, 0);
    });
    services.AddVersionedApiExplorer(o =>
    {
        o.GroupNameFormat = "'v'VVV";
        o.SubstituteApiVersionInUrl = true;
    });

    services.AddSwaggerGen(options =>
    {
        options.DocInclusionPredicate((docName, apiDesc) =>
        {
            if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo))
            {
                return false;
            }

            IEnumerable<ApiVersion> versions = methodInfo.DeclaringType
                .GetCustomAttributes(true)
                .OfType<ApiVersionAttribute>()
                .SelectMany(a => a.Versions);

            return versions.Any(v => $"v{v.ToString()}" == docName);
        });

        options.SwaggerDoc("v1.0", new OpenApiInfo { Title = "My API", Version = "v1.0" });
        options.SwaggerDoc("v2.0", new OpenApiInfo { Title = "My API", Version = "v2.0" });
    });
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();
    app.UseApiVersioning();
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1.0/swagger.json", "V1 Docs");
        c.SwaggerEndpoint("/swagger/v2.0/swagger.json", "V2 Docs");
    });

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}
like image 111
Rena Avatar answered Nov 12 '22 19:11

Rena