Swashbuckle.webapi: Unable to use the same controller names (in different namespaces)

Created on 7 May 2015  路  8Comments  路  Source: domaindrivendev/Swashbuckle.WebApi

I am trying to setup an API that supports multiple API versions. I want to use the namespace to differentiate controllers i.e.

Controllers/V1/ValuesController.cs
Controllers/V2/ValuesController.cs

Unfortunately this breaks the Swagger UI.

If I rename the controllers to include the version (i.e. ValuesV1Controller) it works. However this affects the readability of the documentation. Also, being difficult, I don't like the class name. I wouldn't mind ValuesControllerV1 but this also breaks the UI.

Most helpful comment

We use the following in our initial start up class

SwaggerSpecConfig.Customize(c =>
{
    c.IgnoreObsoleteActions();

    // provides multiple api version support
    c.SupportMultipleApiVersions(
        new[] { "v2", "v3" },
        VersionHelper.ResolveVersionSupportByControllerDescriptor);
});

// enables a drop down for version selection
SwaggerUiConfig.Customize(c => c.EnableDiscoveryUrlSelector());

The "ResolveVersionSupportByControllerDescriptor" is in a static class and handles what shows up in the UI

public static class VersionHelper
{
private const string VersionRegex = @"v([\d]+)";

public static bool ResolveVersionSupportByControllerDescriptor(ApiDescription apiDesc, string   targetApiVersion)
{
// remove any Version text from the tags
apiDesc.ActionDescriptor.ControllerDescriptor.ControllerName = Regex.Replace(apiDesc.ActionDescriptor.ControllerDescriptor.ControllerName, VersionRegex, string.Empty, RegexOptions.IgnoreCase);

// now filter out any controllers that aren't the target version
var controllerNamespace = apiDesc.ActionDescriptor.ControllerDescriptor.ControllerType.FullName;
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(controllerNamespace, string.Format(".{0}.", targetApiVersion), CompareOptions.IgnoreCase) >= 0;
}
}

All 8 comments

We had the same issue and just had to add the v1, v2 to the class names as well to make them unique, then used an operation filter to detect and strip the v1, v2 from the displayed controller name.
Was hoping to take a look into this further when time allowed to see if there were better options.

Paul, thanks a lot for the info. It's nice to know I am not alone ;) Using an operation filter feels messy, it would be great if there is a fix for this. Can I ask how you changed the controller description in the operation filter? I am trying to set apiDescription.ActionDescriptor.ControllerDescriptor.ControllerName but this only works after I reload the page.

Can you elaborate on exactly what you mean by "breaks the Swagger UI". Do the operations just not show up?

If so, then you're likely experiencing the same issue described in #303 and others. Assuming this is your problem, my answer is that this is NOT a bug in Swashbuckle but in fact an issue with overriding IHttpControllerSelector to support versioning by namespace. This approach is advised against by the WebApi because it breaks the WebApi metadata layer - ApiExplorer, and thus the Swagger output.

Let me know if this is what's happening in your case. If so, I'll close the issue because it's not a SB bug. Otherwise, we can investigate further.

Regarding what I mean by "breaks the Swagger UI", you are correct that I mean't that the operations are not showing up.

My issue appears to be slightly different to #303 in my implementation of versioning. I am using attribute based routes to route to the correct controller, so each controller is marked with a RoutePrefix attribute that contains the version number as part of the Url and I am calling MapHttpAttributeRoutes to register the routes.

I think this is a pretty standard approach and it appears to work (apart from the swagger documentation not generating of course).

Apologies, my last statement was incorrect. It is not just the swagger documentation that does not work when you use the same controller name. I have tracked the problem down to the DefaultHttpControllerSelector InitializeControllerInfoCache method in System.Web.Http. It is incorrectly removing controllers with the same name regardless that they are in different namespaces. I'm going to see if I can implement a custom IHttpControllerSelector that doesn't break the ApiExplorer ;)

From memory the default controller selector retrieves controllers along with namespace (which would then have our versioned namespace) and I thought this is great, that should make them unique, but uniqueness is still based on controller class name so the same name means a duplicate and is removed. When I get to work I'll grab some better detail.

We use the following in our initial start up class

SwaggerSpecConfig.Customize(c =>
{
    c.IgnoreObsoleteActions();

    // provides multiple api version support
    c.SupportMultipleApiVersions(
        new[] { "v2", "v3" },
        VersionHelper.ResolveVersionSupportByControllerDescriptor);
});

// enables a drop down for version selection
SwaggerUiConfig.Customize(c => c.EnableDiscoveryUrlSelector());

The "ResolveVersionSupportByControllerDescriptor" is in a static class and handles what shows up in the UI

public static class VersionHelper
{
private const string VersionRegex = @"v([\d]+)";

public static bool ResolveVersionSupportByControllerDescriptor(ApiDescription apiDesc, string   targetApiVersion)
{
// remove any Version text from the tags
apiDesc.ActionDescriptor.ControllerDescriptor.ControllerName = Regex.Replace(apiDesc.ActionDescriptor.ControllerDescriptor.ControllerName, VersionRegex, string.Empty, RegexOptions.IgnoreCase);

// now filter out any controllers that aren't the target version
var controllerNamespace = apiDesc.ActionDescriptor.ControllerDescriptor.ControllerType.FullName;
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(controllerNamespace, string.Format(".{0}.", targetApiVersion), CompareOptions.IgnoreCase) >= 0;
}
}

Thanks. I went with the approach of customising the UI using GroupActionsBy in the end:

public static void GroupActionsUsingFriendlyControllerName(this SwaggerDocsConfig config)
{
    config.GroupActionsBy(SwaggerGroupByFunctions.GroupByFriendlyControllerNameFunc);
}

public static Func<ApiDescription, string> GroupByFriendlyControllerNameFunc
    {
        get
        {
            return apiDescription =>
            {
                var controllerName = apiDescription.ActionDescriptor.ControllerDescriptor.ControllerName;

                // Put in spaces
                controllerName = Regex.Replace(controllerName, "([a-z]|[A-Z]{2,})([A-Z])", @"$1 $2");

                controllerName = Regex.Replace(controllerName, @"\sv\d+", String.Empty, RegexOptions.IgnoreCase);

                return controllerName;
            };
        }
    }

I am going to mark this issue as closed as I believe it is a problem with Web.API rather than Swashbuckle.

Thanks for all your help.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

josephearl picture josephearl  路  4Comments

kongres picture kongres  路  4Comments

jaxidian picture jaxidian  路  4Comments

DotNetRockStar picture DotNetRockStar  路  3Comments

guidoffm picture guidoffm  路  5Comments