46

I'm using Swashbuckle to enable the use of swagger and swagger-ui in my WebApi project.

In the following image you can see two of my controllers shown in the swagger-ui page. These are named as they are in the C# code, however I was wondering if there was a way to change what is shown here?

This is mainly because as you can see ManagementDashboardWidget is not a user friendly name, so I want to change it to be user friendly.

swagger-ui

jtate
  • 2,612
  • 7
  • 25
  • 35
XN16
  • 5,679
  • 15
  • 48
  • 72
  • Have you tried using swagger's @Api as an annotation for your restful service class? – Sampada May 09 '16 at 05:48
  • @Sampada I am unsure how to access that, could you give me a bit more information please? – XN16 May 09 '16 at 07:06
  • I am sorry @API is available for swagger in Java, not in swashbuckle swagger. – Sampada May 09 '16 at 07:44
  • You may be able to achieve your goal simply by naming the .cs file to match your intentions. For example name your controller code file to GroupNameGoesHereController.cs and in swagger you will see the group named GroupNameGoesHere. Unfortunately, that doesn't support spaces in the name so if you want that your better off going with alternative options. – Sn3akyP3t3 Jul 21 '21 at 22:58
  • Renaming the .cs cannot be applied in my case. For example, I have different controller classes for a same route, because of the DI instanciation which differs a lot. – Larry Jul 22 '21 at 13:20

6 Answers6

33

Starting with ASP.NET Core 6, you can use the TagsAttribute either on Controller level:

[Tags("entity")]
[ApiController]
public class DerivedEntitiesController : ControllerBase
{

or on Action level:

[Tags("entity")]
[HttpPut("entity/{key}")]
public IActionResult PutEntity(Guid key, [FromBody] Entity entity)
{

Swagger will group according to the Tags and respect API Versioning.

jtate
  • 2,612
  • 7
  • 25
  • 35
Raul
  • 2,745
  • 1
  • 23
  • 39
20

I tried using venerik's answer, but it still kept the original controller name in the UI along with the new tag that you specify. I also didn't like that you had to add an attribute to every function, so I came up with a solution where you only have to add an attribute to the controller. There are two steps:

Add DisplayNameAttribute on the controller:

[DisplayName("Your New Tag")]
public class YourController : ApiController
{
    // ...
}

Then in the Swagger configuration, you can override the base functionality using the GroupActionsBy function to pull the name that you specified in that attribute:

GlobalConfiguration.Configuration
    .EnableSwagger(c => {
    
        c.GroupActionsBy(apiDesc => {
            var attr = apiDesc
                .GetControllerAndActionAttributes<DisplayNameAttribute>()
                .FirstOrDefault();
                
            // use controller name if the attribute isn't specified
            return attr?.DisplayName ?? apiDesc.ControllerName(); 
        });
        
    })
    .EnableSwaggerUi(c => {
        // your UI config here
    });

ControllerName() is an extension method defined in the Swagger-Net library. If you aren't using that you can also get the controller name from apiDesc.ActionDescriptor.ControllerDescriptor.ControllerName

jtate
  • 2,612
  • 7
  • 25
  • 35
  • For me, `apiDesc.ControllerName` isn't a method. Maybe I'm using a different version of `System.Web.Http`... – Cody Stott May 10 '19 at 19:10
  • 5
    @CodyStott `ControllerName()` is an extension method defined in the [Swagger-Net](https://github.com/heldersepu/Swagger-Net) library, which is another library very similar to Swashbuckle (which is what I assume you are using). Having said that, you should able to use `apiDesc.ActionDescriptor.ControllerDescriptor.ControllerName` to get the controller name. – jtate May 10 '19 at 19:42
20

Version 5 of Swashbuckle supports the following option (to be used in the call to AddSwaggerGen()):

options.TagActionsBy(api => new[] { api.GroupName });

This should be used in combination with the following attribute on your controllers or actions:

[ApiExplorerSettings(GroupName = "...")]

However, by default the group name is used to include the operation in a specific document. So this may lead to unexpected results (depends on your naming of the document in calls to options.SwaggerDoc(name, ...)).

To make it work you may have to add this:

options.DocInclusionPredicate((name, api) => true);

Here, for every operation, name is the name of a document and the group name is available in api.GroupName. To include the operation in the document regardless of its group name, simply return true.

Michiel van Oosterhout
  • 22,839
  • 15
  • 90
  • 132
  • 1
    Should probably point out that by setting the `DocInclusionPredicate` to `true` then should you need to include [Multiple Documents](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/README.md#generate-multiple-swagger-documents) in the future, then you'd need to find another way. – oatsoda Jan 21 '21 at 14:36
19

You can use tags for that. By default Swashbuckle adds a tag with the name of the controller to every operation. You can override that with the SwaggerOperationAttribute. For instance, the next line replaces the default tag, Values, with the tag Test:

public class ValuesController : ApiController
{
    [SwaggerOperation(Tags = new[] { "Test" })]
    public IHttpActionResult Get()
    {
        // ...
    }
}

The Getoperation will now be put in the group Test.

If you want the operation to appear in multiple groups you can add more tags. For instance:

[SwaggerOperation(Tags = new[] { "Test", "Release1" })]

will put the Get operation in the groups Test and Release1.

venerik
  • 5,766
  • 2
  • 33
  • 43
  • That's exactly what I wanted... I just never found much documentation on that part. Thanks very much. – XN16 May 09 '16 at 15:36
  • 5
    This works, but it still leaves the controller name as a tag in the UI, but with no actions under it. For example, say I have `ABCController` but I've added `[SwaggerOperation(Tags = new[] { "DEF" })]` to all routes, the UI shows "ABC" and "DEF", but there is nothing under "ABC". How do we get rid of "ABC" altogether? – jtate Apr 18 '19 at 15:54
  • Take a look to my answer below. I hope it helps you! – Diego B Jan 16 '21 at 00:04
18

By default if I have a controller called ShippingController then the swagger generated UI with name "Shipping"

I was looking to change the name of my controller to something more friendly or in another language. The best I could find was to use a SwaggerOperation attribute to change the name, but this has the limitation that it is a method level attribute and I dont really want to specify the name on every method.

So, I created a convention class to use with controller Route attribute, that we usually use on our controllers and then have the swagger use that to be the name of the controller. Well you know there is a name property on the attribute but the generated swagger doesnt seem to use it.

STEP 1: Create this class:

When the app starts up it will run this and I will be able to look up the Route attribute on my controllers if they have the attribute specified and then use the name property to change the name of the Controller.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace RestServices.Configuration.ConfigInstallers.Conventions
{
    public class ControllerDocumentationConvention : IControllerModelConvention
    {
        void IControllerModelConvention.Apply(ControllerModel controller)
        {
            if (controller == null)
                return;

            foreach (var attribute in controller.Attributes)
            {
                if (attribute.GetType() == typeof(RouteAttribute))
                {
                    var routeAttribute = (RouteAttribute)attribute;
                    if (!string.IsNullOrWhiteSpace(routeAttribute.Name))
                        controller.ControllerName = routeAttribute.Name;
                }
            }

        }
    }
}

STEP 2: Startup.cs:

Modify the StartUp.cs file and in the configure services we need to register our class in the Conventions list. See below:

services.AddControllers(o =>
{
   o.Conventions.Add(new ControllerDocumentationConvention());
});

STEP 3: In each controller add name property in Route Attribute:

[ApiController]
[ApiVersion("1.0")]
[ApiExplorerSettings(IgnoreApi = false, GroupName = "v1")]
[Route("api/Envios/{version:apiVersion}", Name =  "Envios", Order = 1)]
[Produces("application/json")]
public class ShippingController

Now when I run the app and my swagger is generated you can see the controller name is changed to be the same as the text in the route attributes name property.

enter image description here

Diego B
  • 1,256
  • 11
  • 19
  • 5
    That is very elegant. Great suggestion! – Elmar Jan 12 '21 at 21:19
  • 1
    Worked a treat! – Declan McNulty Jan 26 '21 at 19:21
  • 1
    Good solution, also usable with NSwag, where `options.TagActionsBy` not available. Little improvement to not missuse `name`-property from Route-attribute, which seems not being the best matching attribute for me to use for that: I'm using the `ApiExplorerSettingsAttribute` to set the group name instead and using this attribute in the `ControllerDocumentationConvention`. – guppy81 Jan 21 '22 at 17:14
  • 1
    This is unbelievably neat! Should be marked as the solution. Especially because it does not touch the swagger documentation groups and tags. – Marlon Assef Sep 01 '22 at 05:04
4

If one wants to do this at a Controller / Class level, the following is a very useful extract from here

use the [ApiExplorerSettings(GroupName = "Group")] on your controllers

Then in Startup

services.AddSwaggerGen(options =>
{
options.SwaggerDoc(version,
    new Info
    {
        Title = name,
        Version = version
    }
);

options.DocInclusionPredicate((_, api) => !string.IsNullOrWhiteSpace(api.GroupName));

options.TagActionsBy(api => api.GroupName);
});

Also note that

5.0.0-beta of swashbuckle now includes an TagActionsBy overload that supports returning an array of tags. This should allow for the above customizations to be simplified

restj90
  • 111
  • 1
  • 8