How to omit methods from Swagger documentation on WebAPI using Swashbuckle
Asked Answered
K

15

292

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.

Kaffir answered 17/4, 2015 at 14:8 Comment(0)
M
716

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

Meninges answered 12/1, 2017 at 22:24 Comment(12)
Is there a way to do this programmatically? I want to expose an API in some environments but not in others, according to a config setting.Streit
@SyaifulNizamYahya Not sure. Maybe [JsonIgnore] ?Meninges
@Meninges Yes [JsonIgnore] works. Unfortunately, it forbids serialization.Franek
Can you explain what you mean by "forbids serialization"? That attribute is used by the serializer.Meninges
Swashbuckle documentation: Omit Arbitrary OperationsNarcisanarcissism
Is there way to omit constructor?Tindall
System.Web.Http.Description.ApiExplorerSettings, System.Web.HttpHolo
it is still work asp.net mvc int dot net-framework :)Dissimilate
If you need to hide endpoints with some sort of value only available at runtime, you'll want to use a Swagger DocumentFilter.Bandung
Unfortunately this does solve the issue if you're trying to exclude controller actions without a specific verb, you will still get the exception for that error. Is there a way to get around that?Erubescent
Works great, you can even combine it if you have [Obsolete(...)] attibutes you can hide them if you press Ctrl+F in Visual studio, select search & replace, search for [Obsolete( and replace it by [ApiExplorerSettings(IgnoreApi = true), Obsolete(Cask
works great (dotnet 6)Eminent
B
55

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  
Broglie answered 8/6, 2020 at 21:7 Comment(0)
C
29

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.

Caracul answered 14/2, 2017 at 20:8 Comment(1)
I think RemoveRoute might be the droid I'm looking for.Streit
C
16

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>();
});
Coze answered 1/7, 2015 at 11:2 Comment(1)
Do note: this won't remove the path even if you uncomment path.get = null; -- as a result those paths will still be included in the Swagger file but only without the details. It might be better to include the ApiExplorerSettingsAttribute in your answer as you mentioned it in your original reply on GitHub. Using ApiExplorerSettings might also avoid type information from being added to the Swagger file's schemes list.Walling
B
13

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.

Bulk answered 15/12, 2016 at 19:43 Comment(1)
where swaggerDoc comes from?Garnetgarnett
C
12

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

Cube answered 20/2, 2019 at 14:39 Comment(2)
Sweet. Appropriate approach and thank you for sharing the sln.Caia
you do not need custom SwaggerTagAttribute - if you want to remove api, simply loop thru swaggerDoc.Paths and swaggerDoc.Paths.Remove(key)... removal works fine, but ordering of controllers works for me only if controller does not have SwaggerTag attribute, if it has, custom ordering is ignored...Rightward
F
9

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
    });
}
Freshet answered 9/3, 2021 at 6:0 Comment(2)
Error: SwaggerDocument does not contain a definition for Paths ... how to resolve this?Cask
@Cask if you use Swashbuckle.AspNetCore.SwaggerGen.IDocumentFilter it does have .Paths, if you use other one - it has .paths (lower case)Rightward
D
6

If you are using the minimal API you can use:

app.MapGet("/hello", () => "Hello World!").ExcludeFromDescription();
Dieterich answered 4/3, 2022 at 16:47 Comment(0)
A
5

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);
        }
    }
}
Amontillado answered 1/5, 2020 at 9:27 Comment(2)
Error: SwaggerDocument does not contain a definition for Paths ... how to resolve this?Cask
removal works fine.. i tried reorder controllers swaggerDoc.Paths = ...; - it works only if controller does not have [SwaggerTag("some")] attribute. - how to resolve?Rightward
S
4

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>();
    });
}
Sylphid answered 10/9, 2018 at 15:35 Comment(2)
Thanks Aleha. This approach actually works well for SwashBuckle.OData where ApiExplorerSettingsAttribute does not work.Spoilsport
Error: SwaggerDocument does not contain a definition for Paths ... how to resolve this? Also, the compiler has issues with finding pathItem.Get.Cask
N
3

All the solutions proposed are based on IDocumentFilter, which isn't ideal - for me - because it gets called only once everything has been generated and you end up with removing paths but preserving schemas generated because of those paths.

With Net6.0 (may be even with some previous version, I'm not sure) you can work at an earlier stage, like this:

services.AddSwaggerGen(c =>
{
    // all the usual stuff omitted...

    c.DocInclusionPredicate((_, description) =>
    {
        var actionDescriptor = (ControllerActionDescriptor)description.ActionDescriptor;

        return actionDescriptor.ControllerTypeInfo.GetCustomAttributes<ShowInSwaggerAttribute>().Any()
               || actionDescriptor.MethodInfo.GetCustomAttributes<ShowInSwaggerAttribute>().Any();

        //or any other visibility strategy...
    });
});

This will remove unwanted paths and related schemas too.

Naamana answered 4/8, 2023 at 15:36 Comment(1)
very short and effective, can include/exclude controller by name on the fly depending on configurationRightward
M
2

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);
    }
Marla answered 26/5, 2021 at 22:26 Comment(0)
N
1

[NonAction] Indicates that a controller method is not an action method. It belongs to the namespace Microsoft.AspNetCore.Mvc https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.nonactionattribute

In this example of implementation of IActionFilter on a Controller the Swagger generation works fine. Without NonAction Swagger throws and exception Ambiguous HTTP method for action... Actions require an explicit HttpMethod binding for Swagger/OpenAPI 3.0

public class MyController
{
    
    [HttpGet]
    public async Task<int> CountAsync()
    {
        return 1;
    }
    
    [NonAction]
    public void OnActionExecuting(ActionExecutingContext context){ }

    [NonAction]
    public void OnActionExecuted(ActionExecutedContext context) { }
    
}
Nagle answered 7/8, 2023 at 9:54 Comment(0)
W
0

If you're using the new Minimal API approach, you can use .ExcludeFromDescription(); on the EndpointBuilder for the method you want to exclude from the documentation. For example, this excludes the GET method at the "/greeting" endpoint:

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

Documentation is here: RouteHandlerBuilder.ExcludeFromDescription

Wordy answered 15/4, 2023 at 18:25 Comment(0)
B
0

For Net6 and to avoid removing all paths if you have several http methods for the same path

public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
    foreach (var apiDescription in context.ApiDescriptions)
    {
        if (!apiDescription.ActionDescriptor.FilterDescriptors.Any(d => d.Filter is ApiDocumentationAttribute))
        {
            var route = "/" + apiDescription.RelativePath?.TrimEnd('/');
            var path = swaggerDoc.Paths[route];
            if (path != null)
            {
                if (Enum.TryParse<OperationType>(apiDescription.HttpMethod, true, out var operationType))
                {
                    path.Operations.Remove(operationType);
                }
            }
        }
    }
}
Blackamoor answered 18/4 at 9:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.