How to omit methods from Swagger documentation on WebAPI using Swashbuckle

C#asp.net.NetSwagger

C# Problem Overview


I have a C# ASP.NET WebAPI application with API documentation being automatically generated using Swashbuckle. I want to be able to omit certain methods from the documentation but I can't seem to work out how to tell Swagger not to include them in the Swagger UI output.

I sense it is something to do with adding a model or schema filter but it isn't obvious what to do and the documentation only seems to provide examples of how to modify the output for a method, not remove it completely from the output.

C# Solutions


Solution 1 - C#

You can add the following attribute to Controllers and Actions to exclude them from the generated documentation: [ApiExplorerSettings(IgnoreApi = true)]

Solution 2 - C#

May help somebody but during development (debugging) we like to expose whole Controllers and/or Actions and then hide these during production (release build)

#if DEBUG
    [ApiExplorerSettings(IgnoreApi = false)]
#else
    [ApiExplorerSettings(IgnoreApi = true)]
#endif  

Solution 3 - C#

Someone posted the solution on github so I'm going to paste it here. All credits goes to him. https://github.com/domaindrivendev/Swashbuckle/issues/153#issuecomment-213342771

Create first an Attribute class

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class HideInDocsAttribute : Attribute
{
}

Then create a Document Filter class

public class HideInDocsFilter : IDocumentFilter
{
	public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
	{
		foreach (var apiDescription in apiExplorer.ApiDescriptions)
		{
			if (!apiDescription.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<HideInDocsAttribute>().Any() && !apiDescription.ActionDescriptor.GetCustomAttributes<HideInDocsAttribute>().Any()) continue;
			var route = "/" + apiDescription.Route.RouteTemplate.TrimEnd('/');
			swaggerDoc.paths.Remove(route);
		}
	}
}

Then in Swagger Config class, add that document filter

public class SwaggerConfig
{
	public static void Register(HttpConfiguration config)
	{
		var thisAssembly = typeof(SwaggerConfig).Assembly;

		config
			 .EnableSwagger(c =>
				{
					...                       
					c.DocumentFilter<HideInDocsFilter>();
					...
				})
			.EnableSwaggerUi(c =>
				{
					...
				});
	}
}

Last step is to add [HideInDocsAttribute] attribute on the Controller or Method you don't want Swashbuckle to generate documentation.

Solution 4 - C#

You can remove "operations" from the swagger document after it's generated with a document filter - just set the verb to null (though, there may be other ways to do it as well)

The following sample allows only GET verbs - and is taken from this issue.

class RemoveVerbsFilter : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
    {
        foreach (PathItem path in swaggerDoc.paths.Values)
        {
            path.delete = null;
            //path.get = null; // leaving GET in
            path.head = null;
            path.options = null;
            path.patch = null;
            path.post = null;
            path.put = null;
        }
    }
}

and in your swagger config:

...EnableSwagger(conf => 
{
    // ...

    conf.DocumentFilter<RemoveVerbsFilter>();
});

Solution 5 - C#

Make a filter

public class SwaggerTagFilter : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
    {
        foreach(var contextApiDescription in context.ApiDescriptions)
        {
            var actionDescriptor = (ControllerActionDescriptor)contextApiDescription.ActionDescriptor;
            
            if(!actionDescriptor.ControllerTypeInfo.GetCustomAttributes<SwaggerTagAttribute>().Any() && 
               !actionDescriptor.MethodInfo.GetCustomAttributes<SwaggerTagAttribute>().Any())
            {
                var key = "/" + contextApiDescription.RelativePath.TrimEnd('/');
                swaggerDoc.Paths.Remove(key);
            }
        }
    }
}

Make an attribute

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class SwaggerTagAttribute : Attribute
{
}

Apply in startup.cs

services.AddSwaggerGen(c => {
    c.SwaggerDoc(1, new Info { Title = "API_NAME", Version = "API_VERSION" });
    c.DocumentFilter<SwaggerTagFilter>(); // [SwaggerTag]
});

Add [SwaggerTag] attribute to methods and controllers you want to include in Swagger JSON

Solution 6 - C#

I would prefer to remove the dictionary entries for path items completely:

var pathsToRemove = swaggerDoc.Paths
                .Where(pathItem => !pathItem.Key.Contains("api/"))
                .ToList();

foreach (var item in pathsToRemove)
{
    swaggerDoc.Paths.Remove(item.Key);
}

With this approach, you would not get "empty" items in the generated swagger.json definition.

Solution 7 - C#

Like @aleha I wanted to exclude by default so that I didn't accidentally expose an endpoint by accident (secure by default) but was using a newer version of the Swagger that uses OpenApiDocument.

Create a ShowInSwagger Attribute

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class ShowInSwaggerAttribute : Attribute
{}

Then create a Document Filter

using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Reflection;
using System;
using System.Linq;
using TLS.Common.Attributes;

namespace TLS.Common.Filters
{
    public class ShowInSwaggerFilter : IDocumentFilter
    {
        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            foreach (var contextApiDescription in context.ApiDescriptions)
            {
                var actionDescriptor = (ControllerActionDescriptor)contextApiDescription.ActionDescriptor;

                if (actionDescriptor.ControllerTypeInfo.GetCustomAttributes<ShowInSwaggerAttribute>().Any() ||
                    actionDescriptor.MethodInfo.GetCustomAttributes<ShowInSwaggerAttribute>().Any())
                {
                    continue;
                }
                else
                {
                    var key = "/" + contextApiDescription.RelativePath.TrimEnd('/');
                    var operation = (OperationType)Enum.Parse(typeof(OperationType), contextApiDescription.HttpMethod, true);

                    swaggerDoc.Paths[key].Operations.Remove(operation);

                    // drop the entire route of there are no operations left
                    if (!swaggerDoc.Paths[key].Operations.Any())
                    {
                        swaggerDoc.Paths.Remove(key);
                    }
                }
            }
        }
    }
}

then in your startup.cs or ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
     // other code

    services.AddSwaggerGen(c =>
    {
        c.DocumentFilter<ShowInSwaggerFilter>();
        // other config
    });
}

Solution 8 - C#

Based on @spottedmahns answer. My task was vice versa. Show only those that are allowed.

Frameworks: .NetCore 2.1; Swagger: 3.0.0

Added attribute

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class ShowInSwaggerAttribute : Attribute
{
}

And implement custom IDocumentFilter

public class ShowInSwaggerFilter : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
    {

        foreach (var contextApiDescription in context.ApiDescriptions)
        {
            var actionDescriptor = (ControllerActionDescriptor) contextApiDescription.ActionDescriptor;
            
            if (actionDescriptor.ControllerTypeInfo.GetCustomAttributes<ShowInSwaggerAttribute>().Any() ||
                actionDescriptor.MethodInfo.GetCustomAttributes<ShowInSwaggerAttribute>().Any())
            {
                continue;
            }
            else
            {
                var key = "/" + contextApiDescription.RelativePath.TrimEnd('/');
                var pathItem = swaggerDoc.Paths[key];
                if(pathItem == null)
                    continue;

                switch (contextApiDescription.HttpMethod.ToUpper())
                {
                    case "GET":
                        pathItem.Get = null;
                        break;
                    case "POST":
                        pathItem.Post = null;
                        break;
                    case "PUT":
                        pathItem.Put = null;
                        break;
                    case "DELETE":
                        pathItem.Delete = null;
                        break;
                }

                if (pathItem.Get == null  // ignore other methods
                    && pathItem.Post == null 
                    && pathItem.Put == null 
                    && pathItem.Delete == null)
                    swaggerDoc.Paths.Remove(key);
            }
        }
    }
}

ConfigureServices code:

public void ConfigureServices(IServiceCollection services)
{
     // other code

    services.AddSwaggerGen(c =>
    {
        // other configurations
        c.DocumentFilter<ShowInSwaggerFilter>();
    });
}

Solution 9 - C#

Add one line to the SwaggerConfig

c.DocumentFilter<HideInDocsFilter>();

...

public class HideInDocsFilter : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
    { 
        var pathsToRemove = swaggerDoc.Paths
            .Where(pathItem => !pathItem.Key.Contains("api/"))
            .ToList();
    
        foreach (var item in pathsToRemove)
        {
            swaggerDoc.Paths.Remove(item.Key);
        }
    }
}

Solution 10 - C#

You can create a custom filter at both Controller and Method level. So any Controller/Method with your attribute will be available in the Swagger doc. This filter also removed the duplicate HTTP verbs from your document (in this example I make it for GET/PUT/POST/PATCH only), however, you can always customize per your requirement

The attribute

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class PublicApi:Attribute
{

}

Document filter

public class PublicApiFilter : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
    {

        var publicPaths = new List<string> {"/api"};

        var publicApiDescriptions = new List<ApiDescription>();

        var publicMethods = FilterByPublicControllers(swaggerDoc, apiExplorer, publicPaths, publicApiDescriptions);

        FilterByPublicActions(swaggerDoc, publicApiDescriptions, publicMethods);
    }

    private static Dictionary<string, List<string>> FilterByPublicControllers(SwaggerDocument swaggerDoc, IApiExplorer apiExplorer, List<string> publicPaths, List<ApiDescription> publicApiDescriptions)
    {
        var publicMethods = new Dictionary<string, List<string>>();
        foreach (var apiDescription in apiExplorer.ApiDescriptions)
        {
            var isPublicApiController = apiDescription.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<PublicApi>().Any();
            var isPublicApiMethod = apiDescription.ActionDescriptor.GetCustomAttributes<PublicApi>().Any();


            if (!isPublicApiController && !isPublicApiMethod)
            {
                continue;
            }

            var relativePath = ToRelativePath(apiDescription);

            publicPaths.Add(relativePath);
            publicApiDescriptions.Add(apiDescription);

            var action = apiDescription.ActionDescriptor.ActionName;
            List<string> available = null;
            if (!publicMethods.TryGetValue(relativePath, out available))
                publicMethods[relativePath] = new List<string>();
            publicMethods[relativePath].Add(action);
        }

        swaggerDoc.paths = swaggerDoc.paths.Where(pair => publicPaths.Contains(pair.Key))
            .ToDictionary(pair => pair.Key,
                pair => pair.Value);
        return publicMethods;
    }

    private static void FilterByPublicActions(SwaggerDocument swaggerDoc, List<ApiDescription> publicApis, Dictionary<string, List<string>> publicMethods)
    {
        foreach (var api in publicApis)
        {
            var relativePath = ToRelativePath(api);
            var availableActions = publicMethods[relativePath];
            if (availableActions == null)
            {
                continue;
            }

            foreach (var path in swaggerDoc.paths.Where(pair => pair.Key.IndexOf(relativePath) > -1).ToList())
            {
                if (!availableActions.Contains("Get"))
                    path.Value.get = null;
                if (!availableActions.Contains("Post"))
                    path.Value.post = null;
                if (!availableActions.Contains("Put"))
                    path.Value.put = null;
                if (!availableActions.Contains("Patch"))
                    path.Value.patch = null;
            }
        }
    }

    private static string ToRelativePath(ApiDescription apiDescription)
    {
        return "/" + apiDescription.RelativePath.Substring(0,apiDescription.RelativePath.LastIndexOf('/'));
    }
}

And finally, register your SwaggerConfig

public class SwaggerConfig
{
    public static void Register()
    {

        var thisAssembly = typeof(SwaggerConfig).Assembly;
        GlobalConfiguration.Configuration
            .EnableSwagger(c =>
                {
                    c.SingleApiVersion("v1", "Reports");
                    c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
                    c.DocumentFilter<PublicApiFilter>();
                })
            .EnableSwaggerUi(c =>
                {

                });

    }
}

Examples

Controller

[PublicApi]
public class ProfileController : ApiController

Method

 public class UserController : ApiController
 {
    [PublicApi]
    public ResUsers Get(string sessionKey, int userId, int groupId) {
        return Get(sessionKey, userId, groupId, 0);
    }

Solution 11 - C#

If you are using the minimal API you can use:

app.MapGet("/hello", () => "Hello World!").ExcludeFromDescription();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSteveWilkinsonView Question on Stackoverflow
Solution 1 - C#mikesigsView Answer on Stackoverflow
Solution 2 - C#Soeren PedersenView Answer on Stackoverflow
Solution 3 - C#Paulo PozetiView Answer on Stackoverflow
Solution 4 - C#Dave TransomView Answer on Stackoverflow
Solution 5 - C#Rowan StringerView Answer on Stackoverflow
Solution 6 - C#Denis BiondicView Answer on Stackoverflow
Solution 7 - C#GavKilbrideView Answer on Stackoverflow
Solution 8 - C#alehaView Answer on Stackoverflow
Solution 9 - C#VikramrajView Answer on Stackoverflow
Solution 10 - C#Quý NguyễnView Answer on Stackoverflow
Solution 11 - C#JckView Answer on Stackoverflow