Ramya5108
Ramya5108

Reputation: 11

Issue with Swagger Docs generated on api versioning support on a single controller

I have two Swagger docs generated by Swashbuckle, namely docs/v1 and docs/v2. However docs/v2 doesn't provide information on the action GetV2(). Please help if Swashbuckle has an option to address this.

1.Since the route template appears to be same for actions get() and getv2(), docs v2 doesn't show any info about getV2().

2. Swagger definition doesn't look v1.0/get while appears as v{version}/get in docs/v1

Note:I have refered on apiversioning samples, but not sure what am I missing. All samples refer to Swashbuckle.core while i use Swashbuckle.

[ApiVersion("1.0")] 
[ApiVersion("2.0")]
public class HelloController : ApiControllerBase
{
    [MapToApiVersion("1.0")]
    [Route("v{version:apiVersion}/get")]
    [HttpGet]
    public ProjectSightActionResult Get()
    {
        return new Ok("Version 1.0");
    }

    [MapToApiVersion("2.0")]
    [Route("v{version:apiVersion}/get")]
    [HttpGet]
    public ProjectSightActionResult GetV2()
    {
        return new Ok("Version 2.0");
    }
}

This is my controller including two actions, one for version v1 and one for v2. Below is the webapi.config for the route constraint:

public static void Register(HttpConfiguration config)
{
    // Web API configuration and services

    // Version Start 
    // https://github.com/Microsoft/aspnet-api-versioning/wiki/Versioning-via-the-URL-Path
    // added to the web api configuration in the application setup
    var constraintResolver = new DefaultInlineConstraintResolver()
    {
        ConstraintMap = {["apiVersion"] = typeof( ApiVersionRouteConstraint )}
    };

    config.MapHttpAttributeRoutes(constraintResolver);
    config.AddApiVersioning();
    // Version End 

    // Web API routes
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
    config.Filters.Add(new AuthenticationFilter());


    // This causes Web API to remove the IPrincipal from any request that enters the Web API pipeline. Effectively, it "un-authenticates" the request. 
    // https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/authentication-filters
    config.SuppressHostPrincipal();
}

My Swagger config has the code:

[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]
namespace sample.WebAPI
{
    public class SwaggerConfig
    {
        public static void Register()
        {
            var thisAssembly = typeof(SwaggerConfig).Assembly;

             GlobalConfiguration.Configuration
                .EnableSwagger(c =>
                    {
                    c.MultipleApiVersions(
                        (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion),
                               vc =>
                                 {
                                     vc.Version("v1", "sample.WebAPI");
                                     vc.Version("v2", "sample.WebAPI");
                                 });
                    }
                )
                .EnableSwaggerUi(c =>
                    {            
                        c.EnableDiscoveryUrlSelector();

                       // If your API supports ApiKey, you can override the default values.
                       // "apiKeyIn" can either be "query" or "header"                                                

                        c.EnableApiKeySupport("x-jwt-assertion", "header");
                      });
        }

        private static string GetXmlCommentsPath()
        {
            return string.Format(@"{0}\bin\XmlComments.xml", AppDomain.CurrentDomain.BaseDirectory);
        } 

        private static bool ResolveVersionSupportByRouteConstraint(ApiDescription apiDesc, string targetApiVersion)
        {
            //check for deprecated versions
            var controllerVersionAttributes = apiDesc.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<ApiVersionAttribute>(true);
            if (!controllerVersionAttributes.Any())
            {
                return true; // include when no attributes are defined
            }

            if (targetApiVersion.StartsWith("v"))
            {
                targetApiVersion = targetApiVersion.Substring(1); // remove the leading "v" in `v{x.x}`
            }

            var apiVersion = ApiVersion.Parse(targetApiVersion);

            var controllerApiVersion = controllerVersionAttributes
                .Where(x => x.Versions.Contains(apiVersion))
                .FirstOrDefault();

            // has a compatible version, now check the action for [MapToApiVersion]
            if (controllerApiVersion != null)
            {
                var actionMapToAttributes = apiDesc.ActionDescriptor.GetCustomAttributes<MapToApiVersionAttribute>(false);
                if (!actionMapToAttributes.Any())
                {
                    return true; // no MapTo attributes matched, then include the action
                }

                if (actionMapToAttributes.Any(x => x.Versions.Contains(apiVersion)))
                {
                    return true; // include mapped action
                }
            }

            return false;
        }
    }
}

Upvotes: 1

Views: 2526

Answers (1)

Chris Martinez
Chris Martinez

Reputation: 4418

I'm not sure if you ever solved your problem, but you can now use the official API Explorer for API versioning, which makes Swagger integration simple. You can see a complete working example here.

Here's an abridged version that should work for you:

static void Register( HttpConfiguration configuration )
{
    var constraintResolver = new DefaultInlineConstraintResolver() { ConstraintMap = { ["apiVersion"] = typeof( ApiVersionRouteConstraint ) } };

    configuration.AddApiVersioning();
    configuration.MapHttpAttributeRoutes( constraintResolver );

    // note: this option is only necessary when versioning by url segment.
    // the SubstitutionFormat property can be used to control the format of the API version
    var apiExplorer = configuration.AddVersionedApiExplorer( options => options.SubstituteApiVersionInUrl = true );

    configuration.EnableSwagger(
                    "{apiVersion}/swagger",
                    swagger =>
                    {
                        // build a swagger document and endpoint for each discovered API version
                        swagger.MultipleApiVersions(
                            ( apiDescription, version ) => apiDescription.GetGroupName() == version,
                            info =>
                            {
                                foreach ( var group in apiExplorer.ApiDescriptions )
                                {
                                    var description = "A sample application with Swagger, Swashbuckle, and API versioning.";

                                    if ( group.IsDeprecated )
                                    {
                                        description += " This API version has been deprecated.";
                                    }

                                    info.Version( group.Name, $"Sample API {group.ApiVersion}" )
                                        .Contact( c => c.Name( "Bill Mei" ).Email( "[email protected]" ) )
                                        .Description( description )
                                        .License( l => l.Name( "MIT" ).Url( "https://opensource.org/licenses/MIT" ) )
                                        .TermsOfService( "Shareware" );
                                }
                            } );

                        swagger.IncludeXmlComments( XmlCommentsFilePath );
                    } )
                 .EnableSwaggerUi( swagger => swagger.EnableDiscoveryUrlSelector() );
    }
}

static string XmlCommentsFilePath
{
    get
    {
        var basePath = System.AppDomain.CurrentDomain.RelativeSearchPath;
        var fileName = typeof( Startup ).GetTypeInfo().Assembly.GetName().Name + ".xml";
        return Path.Combine( basePath, fileName );
    }
}

Upvotes: 1

Related Questions