Reputation: 4030
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.
Upvotes: 306
Views: 222495
Reputation: 7658
And addendum to https://stackoverflow.com/a/76837366/4122889, here's a sample on how to only include publicly facing apis:
The nice thing is that instead of the document filter this is faster and does not leave you with extra schema's.
services.AddSwaggerGen(c =>
{
CommonSwaggerGenConfig(c);
// Only publicly accessible methods
//c.DocumentFilter<PublicApiDocumentFilter>();
c.DocInclusionPredicate((docName, desc) =>
{
// Only apply for the public api
if (docName != "public")
return true;
return desc.ActionDescriptor.EndpointMetadata.Any(x => x is AllowAnonymousAttribute);
});
c.SwaggerDoc("public", new()
{
Title = "...",
...
});
// Remove JWT Authentication for public Swagger document
// No security definitions or requirements for the public document
});
Upvotes: 0
Reputation: 1
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);
}
}
}
}
}
Upvotes: 0
Reputation: 3131
[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) { }
}
Upvotes: 2
Reputation: 3048
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.
Upvotes: 5
Reputation: 23
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
Upvotes: 1
Reputation: 191
If you are using the minimal API you can use:
app.MapGet("/hello", () => "Hello World!").ExcludeFromDescription();
Upvotes: 14
Reputation: 31
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);
}
Upvotes: 2
Reputation: 1569
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
});
}
Upvotes: 9
Reputation: 143
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
Upvotes: 12
Reputation: 101
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);
}
}
}
Upvotes: 5
Reputation: 726
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
Upvotes: 60
Reputation: 8201
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.
Upvotes: 13
Reputation: 8539
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>();
});
}
Upvotes: 4
Reputation: 389
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.
Upvotes: 30
Reputation: 11420
You can add the following attribute to Controllers and Actions to exclude them from the generated documentation: [ApiExplorerSettings(IgnoreApi = true)]
Upvotes: 747
Reputation: 4185
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>();
});
Upvotes: 16