Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple Api Versions with Swagger in MVC 6 when using action constraints

Hopefully somebody has tried something similar with a versioned API in MVC 6 and Swagger to display documentation about the different versions.

I am using the recommended API versioning in MVC 6 as per this ASP.NET 5 repository. The only change I have made is the GetVersion method to read the api version from the request's custom http header:

//in VersionRangeValidator.cs
public static string GetVersion(HttpRequest request)
{
        //return request.Query["version"];
        if (!string.IsNullOrWhiteSpace(request.Headers[Constants.CommonRoutingDefinitions.ApiVersionSegmentName]))
        {
            return request.Headers[Constants.CommonRoutingDefinitions.ApiVersionSegmentName];
        }
        return Constants.CommonRoutingDefinitions.CurrentApiVersion;
 }

and I have a controller like this:

[Route("api/[controller]")]
[Produces(Constants.MediaTypeNames.ApplicationJson)]
public class TagsController : Controller
{
    private readonly ITagService _tagService;

    public TagsController(ITagService tagService)
    {
        _tagService = tagService;
    }

    /// <summary>
    /// Version 1 by default
    /// </summary>
    /// <returns>All the tags</returns>
    [HttpGet]
    [Produces(typeof(IEnumerable<Tag>))]
    public IEnumerable<Tag> GetTags()
    {
        IEnumerable<Tag> tags = _tagService.GetTags();
        return tags;
    }

    /// <summary>
    /// Version 2
    /// </summary>
    /// <returns>All the tags V2</returns>
    [VersionGet("", versionRange: "[2]")]
    public IEnumerable<Tag> GetTagsV2()
    {
        IList<Tag> tags = new List<Tag>
        {
            new Tag { Id = 1, Links = Enumerable.Empty<Link>().ToList(), Name = "Tag version 2" }
        };
        return tags;
    }
}

The versioning happens with a custom http header so that

GET /api/tags

Content-Type: application/json

will hit the GetTags() action by default as no header has been specified and the

GET /api/tags

api-version: 2

Content-Type: application/json

will hit the GetTagsV2() action.

I have added Swagger UI and Swagger GEN libraries following steps at this blog so in my project.json I have de following dependencies:

"Swashbuckle.SwaggerGen": "6.0.0-rc1-final",
"Swashbuckle.SwaggerUi": "6.0.0-rc1-final"

Then in my Startup.cs I add Swagger to the pipeline with

 //inside Configure(IApplicationBuilder app)
 app.UseSwaggerGen();
 app.UseSwaggerUi();

and I configure Swagger as follows:

private void ConfigureSwagger(IServiceCollection services)
{
        services.AddSwaggerGen();

        services.ConfigureSwaggerDocument(options =>
        {
            options.MultipleApiVersions(new Swashbuckle.SwaggerGen.Info[]
            {
                new Swashbuckle.SwaggerGen.Info
                {
                    Version = "v1",
                    Title = "MyApp API",
                    Description = "A RESTful API"
                },
                new Swashbuckle.SwaggerGen.Info
                {
                    Version = "v2",
                    Title = "MyApp API (v2)",
                    Description = "A RESTful API"
                }
            }, (description, version) => {
                //description is an instance of ApiDescription and 
                //version is either "v1" or "v2" 
                //depending on the user choice in swagger UI page
                //TODO, how can I know whether the action belongs to v1 or to v2 to return true or false as appropriate?
            });
            options.OperationFilter(new Swashbuckle.SwaggerGen.XmlComments.ApplyXmlActionComments(Configuration["Documentation:SwaggerDocXml"]));
        });

        services.ConfigureSwaggerSchema(options =>
        {
            options.DescribeAllEnumsAsStrings = true;
            options.ModelFilter(new Swashbuckle.SwaggerGen.XmlComments.ApplyXmlTypeComments(Configuration["Documentation:SwaggerDocXml"]));
        });
}

The problem is that I don't know how to get from the description (which is an instance of Microsoft.AspNet.Mvc.ApiExplorer.ApiDescription) the necessary information to know whether that given action has to be displayed in Swagger UI or not depending on the specified version. Any tip would be greatly appreciated. It would help to understand how the this ASP.NET 5 repository implementation for versioning works because I still don't understand it well and cannot find a good explanation on how the action constraints work.

PS: This stackoverflow question helped me implement the versioning with MVC 6 but I couldn't find much about how Swagger would integrate with this way of versioning an API.

like image 671
diegosasw Avatar asked Jan 03 '16 02:01

diegosasw


2 Answers

My first answer to a question here, hope you find it useful. My apologies for the bad formatting of the code snippet but have little time on my hands now.

You were almost there but you didn't add the filter for version in the description. This works in my implementation for the following url format:

"/v1/Sessions" and "/v3/Sessions"


options.MultipleApiVersions(new Info[]{
new Info
{
        Version = "v1",
        Title = "your_own",
        Description = "Defines the API to access... your_own",
        TermsOfService = "your_own",
        Contact = new Contact()
        {
            Email = "your_own",
            Name = "your_own"
        }
},
new Info
    {
        Version = "v3",
        Title = "your_own",
        Description =
            "Defines the API to .... your_own",
        TermsOfService = "your_own",
        Contact = new Contact()
        {
            Email = "your_own",
            Name = "your_own"
        }
    }}, (description, version) => {
//Here we compare if the version is part of the incoming URI, if yes, show it on swagger page.
return description.RelativePath.ToLower().Contains(version.ToLower()); } );
like image 84
hristiyan.dimov Avatar answered Sep 27 '22 21:09

hristiyan.dimov


It seems that you have a custom VersionGet attribute. You can use this to determine the v2 endpoints. The other endpoints may default to v1 (if this is what you want).

Here's a snippet I use. Note that my custom attribute is named ApiVersion2.

public static bool ResolveVersion(ApiDescription apiDesc, string targetApiVersion)
{
    if (targetApiVersion == "v2")
    {
        ApiVersion2Attribute v2Attr = apiDesc.ActionDescriptor.GetCustomAttributes<ApiVersion2Attribute>().FirstOrDefault();
        if (v2Attr != null)
            return true;
    }

    if (targetApiVersion == "v1")
        return true;

    throw new NotSupportedException();
}
like image 26
Daniel Iancu Avatar answered Sep 27 '22 22:09

Daniel Iancu