Swashbuckle.webapi: Exclude controllers methods from docs without using the Obsolete attribute #90

Created on 29 Dec 2014  路  24Comments  路  Source: domaindrivendev/Swashbuckle.WebApi

If i want to Individual Method in Controller instead of whole Controller Ignore what can I do?

Most helpful comment

Swashbuckle is built on top of WebApi's built-in metadata layer - ApiExplorer. If you decorate a controller or action with the following attribute:

[ApiExplorerSettings(IgnoreApi=true)]
public class MyController : ApiController

then this will ultimately cause the entire controller or individual action to be omitted from the Swagger output

All 24 comments

Swashbuckle is built on top of WebApi's built-in metadata layer - ApiExplorer. If you decorate a controller or action with the following attribute:

[ApiExplorerSettings(IgnoreApi=true)]
public class MyController : ApiController

then this will ultimately cause the entire controller or individual action to be omitted from the Swagger output

I have a scenario where I have a BaseController which each API inherits. I want to hide this BaseController. The moment I put the below line all controllers are hidden from the documentation.
[ApiExplorerSettings(IgnoreApi = true)] //Making sure this does not appear in the documentation

How do we handle this?

+1 for hiding just the BaseController.

I've managed to do this by creating custom attribute:

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

and custom IDocumentFilter:

``` c#
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);
        }
    }
}

this way every method that has [HideInDocs] attribute will be excluded.

EDIT: You can apply this attribute to Controller and have all it methods excluded.

@Misiu 馃憤
Your solution works well! I just want to add that you need specify in SwaggerConfig class the following:
c.DocumentFilter<HideInDocsFilter>();

Hello,

This doesnt work for me.

apiDescription.Route.RouteTemplate

is always

api/{controller}/{id}

Therefore, it can not remove it. How do I fix it? I dont want to manually define a new routemap for each controller.

Thank you.

If it was possible to do the opposite from defaults - include only controllers that are decorated with specific attribute. When app has multiple controllers that are not needed in docs and only some that are needed.

@SlyNet
Take a look at the comment from Misiu:
https://github.com/domaindrivendev/Swashbuckle/issues/153#issuecomment-213342771
With a custom IDocumentFilter you can hide or show anything you want.

@SlyNet
Here is an example where we hide ALL and only leave some
c# private class ApplyDocumentVendorExtensions : IDocumentFilter { public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer) { var paths = new Dictionary<string, PathItem>(swaggerDoc.paths); swaggerDoc.paths.Clear(); foreach (var path in paths) { if (path.Key.Contains("foo")) swaggerDoc.paths.Add(path); } } }

For someone that comes across this and is using ODataControllers and not API Controller, I had to update the assigning of route as below. This might also work for the problem @ahmettahasakar ran into.

var route = "/" + apiDescription.RelativePath.TrimEnd('/');

I did something a little different for OData. The solution above was removing more than one method of the controller, often a GET and POST at the same time. I adjusted this a bit so it'll work on the methods it's attached to. If there is a more condensed version that someone can think of, that'll be great.

Would update this more to check to see if all methods in the controller are being ignored, then remove the path.

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

                if (apiDescription.ActionDescriptor.ActionName.ToLower() == "post")
                    swaggerDoc.paths[route].post = null;
                else if (apiDescription.ActionDescriptor.ActionName.ToLower() == "get")
                    swaggerDoc.paths[route].get = null;
                else if (apiDescription.ActionDescriptor.ActionName.ToLower() == "patch")
                    swaggerDoc.paths[route].patch = null;
                else if (apiDescription.ActionDescriptor.ActionName.ToLower() == "delete")
                    swaggerDoc.paths[route].delete = null;
                else
                    swaggerDoc.paths.Remove(route);
            }
        }
    }

Why isn't there just a simple attribute to SKIP a method? I feel like Swashbuckle is a lot harder to use than it needs to be; everything requires building your own document filter when the most common primitives could have been put in the box

@Cloudmersive Don't forget Swashbuckle is OpenSource!
Wanna make it less harder to use? create a fork send a pull request...
...you know, walk the walk or better yet code the code

Did you read the first response to this issue? What鈥檚 your problem with the ApiExplorerSettings attribute?

Might have missed the point that it can be added to individual actions @domaindrivendev ?

To hide a custom BaseController, you can make it abstract.

public abstract class BaseController : ApiController

@rvdb2 abstract class doesn't hide the an custom controller. I'm trying in Azure Service Fabric ASP.NET Core Stateless service.

@waqasdilawar I retried it using a new Web API project in VS2015, with 2 empty controllers: public abstract class CustomBaseController : ApiController, and public CustomController : CustomBaseController. CustomBaseController has one concrete method: public string Get(int id), while CustomController is empty.

If I run the project in VS2015 (using IIS), CustomBaseController.Get is unaccessible and hidden in documentation, while the inherited CustomController.Get is accessible and visible.

If I make CustomBaseController non-abstract, its Get method becomes accessible and visible.

Hopefully this makes it reproducible for you.

@Misiu 馃憤
Your solution works well! I just want to add that i had to change your code from:

var route = "/" + apiDescription.Route.RouteTemplate.TrimEnd('/');
to
var route = "/" + apiDescription.RelativePath.TrimEnd('/');

otherwise RouteTemplate is always

api/{controller}/{id}

Here is a sample ShowInSwagger implementation:

using System.Web.Http;
using Swashbuckle.Application;
using System;
using System.Reflection;
using System.IO;
using System.Linq;
using Swashbuckle.Swagger;
using System.Web.Http.Description;
using System.Collections.Generic;

public class SwaggerConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.EnableSwagger(c =>
            {
                c.DocumentFilter<ShowInSwaggerAttributeFilter>();
                c.SingleApiVersion("v1", "MyName");
                var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
                var commentsFileName = Assembly.GetExecutingAssembly().GetName().Name + ".XML";
                var commentsFile = Path.Combine(baseDirectory, "bin", commentsFileName);
                c.IncludeXmlComments(commentsFile);
            })
        .EnableSwaggerUi(c =>
        {
            c.DisableValidator();
        });
    }
}

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

////https://github.com/domaindrivendev/Swashbuckle/issues/153
public class ShowInSwaggerAttributeFilter : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
    {
        var filteredApis = apiExplorer.ApiDescriptions.Where(a => a.GetControllerAndActionAttributes<ShowInSwaggerAttribute>().Any());

        var paths = new Dictionary<string, PathItem>(swaggerDoc.paths);

        swaggerDoc.paths.Clear();

        foreach (var apiDescription in filteredApis)
        {
            var route = "/" + apiDescription.RelativePathSansQueryString();
            if (paths.Any(p => p.Key == route))
            {
                var path = paths.FirstOrDefault(p => p.Key == route);
                swaggerDoc.paths.Add(path);
            }
        }
    }
}

To use just add the [ShowInSwagger] attribute to the controller class or method.

Improvement to take verb (GET/POST/etc) into consideration:

`public class HideInDocsFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var contextApiDescription in context.ApiDescriptions)
{
var actionDescriptor = (ControllerActionDescriptor)contextApiDescription.ActionDescriptor;

            if (actionDescriptor.ControllerTypeInfo.GetCustomAttributes(typeof(HideInSwaggerAttribute), true).Any() ||
                actionDescriptor.MethodInfo.GetCustomAttributes(typeof(HideInSwaggerAttribute), true).Any())
            {
                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);
                }
            }
        }
    }
}`

Use @ApiExcludeEndpoint(true) for NestJS

I have a scenario where I have a BaseController which each API inherits. I want to hide this BaseController. The moment I put the below line all controllers are hidden from the documentation.
[ApiExplorerSettings(IgnoreApi = true)] //Making sure this does not appear in the documentation

How do we handle this?

I had the same issue. Set your BaseController access modifier to "Protected" instead of "Public". This will hide the Controller showing up in Swagger. It worked for me.

I had similar issue, where i want to show only 1 controller in documentation out of 80+ Controller with 400+ action methods.
my approach is to keep [ApiExplorerSettings(IgnoreApi = true)] on my "ApiBaseController : ApiController" which inherit by all Controllers and then keep [ApiExplorerSettings(IgnoreApi = false)] to that single controller which i want to show in documentation.
Hope this work for you.

Was this page helpful?
0 / 5 - 0 ratings