NSwag ui does not support Microsoft.AspNetCore.Mvc.Versioning?

Created on 6 Mar 2017  路  13Comments  路  Source: RicoSuter/NSwag

In our project we implemented webap versionning (https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx)

I did the same in the NSwag-Samples (see attached project zip file)
So when using the attribute [ApiVersion("1.0")] and the route [Route("api/v{version:apiVersion}/[controller]")] we get after nswag ui generation following url:
/api/v/Persons
This is not correct...
If we click on this url, we get a 404 not found...
The correct url should be
/api/v1/Persons

SampleWebApiCore461WithSwaggerUi with apiversion implementation.zip

enhancement

Most helpful comment

We also needed the Versioning support for WebAPI, I ended up creating something like:

In my Startup.cs:

        var swaggerUiSettings = new SwaggerUiOwinSettings();
        swaggerUiSettings.OperationProcessors.Insert(0, new ApiVersionProcessor());
        app.UseSwaggerUi(typeof(Startup).Assembly, swaggerUiSettings);

ApiVersionProcessor.cs:

public class ApiVersionProcessor : IOperationProcessor
{
    public Task<bool> ProcessAsync(OperationProcessorContext context)
    {
        var versionAttributes = context.MethodInfo.GetCustomAttributes()        // 1) check for attribute on method [MapToApiVersionAttribute]
            .Concat(context.MethodInfo.DeclaringType.GetCustomAttributes())     // 2) check for attribute on type
            .Where(a => a.GetType().Name == "MapToApiVersionAttribute" || a.GetType().Name == "ApiVersionAttribute")
            .Select(a => (dynamic)a)
            .ToArray();

        if (versionAttributes.Any())
        {
            var versionAttribute = versionAttributes.First();
            if (ObjectExtensions.HasProperty(versionAttribute, "Versions"))
            {
                ReplaceApiVersionInPath(context.OperationDescription, versionAttribute.Versions);
            }
        }

        return Task.FromResult(true);
    }

    private void ReplaceApiVersionInPath(SwaggerOperationDescription operationDescription, dynamic versions)
    {
        operationDescription.Path = operationDescription.Path.Replace("{version:apiVersion}", versions[0].ToString());
    }
}

Then in the SwaggerUI:
image

The versioned controller code:

namespace NSwag.Demo.OwinWeb.Controllers.v1
{
    [ApiVersion("1.0")]
    [Route("api/v{version:apiVersion}/animal")]
    public class AnimalController : ApiController
    {
        // GET: api/Animal
        public string Get()
        {
            return "Cat 1.0";
        }
    }
}
namespace NSwag.Demo.OwinWeb.Controllers.v2
{
    [ApiVersion("2.0")]
    [ApiVersion("3.0")]
    [Route("api/v{version:apiVersion}/animal")]
    public class AnimalController : ApiController
    {
        // GET: api/Animal
        public string Get()
        {
            return "Cat 2.0";
        }

        [MapToApiVersion("3.0")]
        public string GetV3()
        {
            return "Cat 3.0";
        }
    }
}

All 13 comments

This is currently not supported...

I think you can write a custom operation filter to support that...

What do you mean by writing a custom operation filter?

We also needed the Versioning support for WebAPI, I ended up creating something like:

In my Startup.cs:

        var swaggerUiSettings = new SwaggerUiOwinSettings();
        swaggerUiSettings.OperationProcessors.Insert(0, new ApiVersionProcessor());
        app.UseSwaggerUi(typeof(Startup).Assembly, swaggerUiSettings);

ApiVersionProcessor.cs:

public class ApiVersionProcessor : IOperationProcessor
{
    public Task<bool> ProcessAsync(OperationProcessorContext context)
    {
        var versionAttributes = context.MethodInfo.GetCustomAttributes()        // 1) check for attribute on method [MapToApiVersionAttribute]
            .Concat(context.MethodInfo.DeclaringType.GetCustomAttributes())     // 2) check for attribute on type
            .Where(a => a.GetType().Name == "MapToApiVersionAttribute" || a.GetType().Name == "ApiVersionAttribute")
            .Select(a => (dynamic)a)
            .ToArray();

        if (versionAttributes.Any())
        {
            var versionAttribute = versionAttributes.First();
            if (ObjectExtensions.HasProperty(versionAttribute, "Versions"))
            {
                ReplaceApiVersionInPath(context.OperationDescription, versionAttribute.Versions);
            }
        }

        return Task.FromResult(true);
    }

    private void ReplaceApiVersionInPath(SwaggerOperationDescription operationDescription, dynamic versions)
    {
        operationDescription.Path = operationDescription.Path.Replace("{version:apiVersion}", versions[0].ToString());
    }
}

Then in the SwaggerUI:
image

The versioned controller code:

namespace NSwag.Demo.OwinWeb.Controllers.v1
{
    [ApiVersion("1.0")]
    [Route("api/v{version:apiVersion}/animal")]
    public class AnimalController : ApiController
    {
        // GET: api/Animal
        public string Get()
        {
            return "Cat 1.0";
        }
    }
}
namespace NSwag.Demo.OwinWeb.Controllers.v2
{
    [ApiVersion("2.0")]
    [ApiVersion("3.0")]
    [Route("api/v{version:apiVersion}/animal")]
    public class AnimalController : ApiController
    {
        // GET: api/Animal
        public string Get()
        {
            return "Cat 2.0";
        }

        [MapToApiVersion("3.0")]
        public string GetV3()
        {
            return "Cat 3.0";
        }
    }
}

@ThumNet, thanks for the code.
Will try that, when working again on this piece of code.
For the moment we do use the Swashbuckle/NswagStudio via swagger.json workaround.

@ThumNet thanks for your help here... maybe we should add this processor as a default processor to NSwag?

@ericvb, you're welcome :)

@rsuter, that would be great! And of course the code can improve overtime to support other versioning configurations.

I think building versioning into NSwag would be a great help.

Thank you for the example above its working for my Swagger UI.

However I also use NSwag to generate the swagger.json and typescript (As this is the main benefit of NSwag over swashbuckle etc for me) and this will still get errors, unless there is a way to provide OperationProcessors to the cmd line?

NSwag.SwaggerGeneration.WebApi.WebApiToSwaggerGenerator.<AddOperationDescriptionsToDocumentAsync>d__12.MoveNext() The method 'Get' on path '/api/v/Monk/{id}' is registered multiple times.

Providing processor types in cmd line is definitely on my wish list...

Hi @ThumNet,

what is exactly the ObjectExtensions.HasProperty method?

Could you please share that code or give any hint on what does exactly?

Something like this perhaps?

private bool HasProperty( object theObject, string propertyName ) {
    var type = theObject.GetType();
    return type.GetProperty( propertyName ) != null;
}

Hi @vdecristofaro, the ObjectExtensions.HasPropery is part of NSwag, you can find it here

I've added the processor to the default processor list (now it even works with NSwagStudio). In v11 it will be enabled by default, but you can remove it from the processors list to disable it...

Please test with the latest CI artifacts if this is working as expected...

Was this page helpful?
0 / 5 - 0 ratings