REST API Visibility Control
Tools such a Swagger are widely used to document REST APIs. REST endpoints are grouped by controller and may have a name (OperationId).
The UI typically shows all REST endpoints, which is not always desirable.
- Hide endpoints that cannot be controlled from the UI.
- Show or hide endpoints based on application context, such as audits or experience/integration levels.
- Reduce endpoints in development for startup optimization and UI reduction.
The following solution approach shows how to show or hide REST endpoints based on configuration.
👉 Hiding an endpoint does not affect its availability; REST clients can still use it without restriction.
Filter Configuration
Endpoint visibility is defined in the appsettings.json application configuration file, where visible and/or invisible filters are set. The following options are available:
- Filter on visible endpoints
- Filter on invisible endpoints
- Combined filter of visible endpoints with a subset of invisible endpoints.
The endpoint filter is an expression in ControllerMask[.OperationMask] format and supports ? and * masks.
Examples of filter expressions:
-
WeatherForecast- all endpoints of theWeatherForecastcontroller -
*Audit- all endpoints of the controller whose name ends withAudit -
WeatherForecast.Get*- all endpoints of the WeatherForecast controller whose operation name begins withGet. -
*.Get*- all endpoints whose operation name begins withGet.
This results in the following usage matrix:
| Mode | Visible | Hidden | Example |
|---|---|---|---|
| Include | ✔️ | ❌ | "VisibleItems": ["User.*", "WeatherForecast.Get*"] |
| Exclude | ❌ | ✔️ | "HiddenItems": ["User.*", "WeatherForecast.DeleteWeatherForecast"] |
| Mixed | ✔️ | ✔️ |
"VisibleItems": ["*.Get*"],"HiddenItems": ["User.Get*"]
|
The filters are defined in the ApiConfiguration section of the configuration file. Example Include filter:
"ApiConfiguration": {
"VisibleItems": [
"User.*",
"WeatherForecast.Get*"
]
}
Example Exclude filter:
"ApiConfiguration": {
"HiddenItems": [
"User.*",
"WeatherForecast.DeleteWeatherForecast"
]
}
Example of an Include filter combined with an Exclude filter:
"ApiConfiguration": {
"VisibleItems": [
"*.Get*"
],
"HiddenItems": [
"User.Get*"
]
}
👉 In development mode, it is recommended that you outsource endpoint configuration to User Secrets.
Filter Convention
ASP.NET provides the ability to define the visibility of endpoints using the ActionModelConvention. The ApiVisibilityConvention implementation controls the visibility of the endpoint based on visible and invisible elements:
internal sealed class ApiVisibilityConvention : IActionModelConvention
{
private List<string> VisibleItems { get; }
private List<string> HiddenItems { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="visibleItems">List of visible items name masks (wildcards: *?)</param>
/// <param name="hiddenItems">List of hidden items name masks (wildcards: *?)</param>
internal ApiVisibilityConvention(
IEnumerable<string>? visibleItems = null,
IEnumerable<string>? hiddenItems = null)
{
VisibleItems = visibleItems != null ? new(visibleItems) : new();
HiddenItems = hiddenItems != null ? new(hiddenItems) : new();
}
public void Apply(ActionModel action)
{
// visible
if (VisibleItems.Count > 0)
{
action.ApiExplorer.IsVisible = VisibleItems.Any(
x => MatchItem(action.Controller.ControllerName,
GetOperationId(action), x));
}
// hidden
if (HiddenItems.Count > 0)
{
if (VisibleItems.Count > 0)
{
// exclude from visible
if (action.ApiExplorer.IsVisible == true)
{
action.ApiExplorer.IsVisible = !HiddenItems.Any(
x => MatchItem(action.Controller.ControllerName,
GetOperationId(action), x));
}
}
else
{
action.ApiExplorer.IsVisible = !HiddenItems.Any(
x => MatchItem(action.Controller.ControllerName,
GetOperationId(action), x));
}
}
}
private static string? GetOperationId(ActionModel action) =>
(action.Attributes.FirstOrDefault(
x => x is HttpMethodAttribute) as HttpMethodAttribute)?.Name;
private static bool MatchItem(string controllerName,
string? operationId, string mask)
{
var controllerMask = mask;
string? actionMask = null;
var actionIndex = mask.IndexOf('.');
if (actionIndex > 0)
{
controllerMask = mask.Substring(0, actionIndex);
actionMask = mask.Substring(actionIndex + 1);
}
// controller mask only
if (actionMask == null || string.IsNullOrWhiteSpace(operationId))
{
return MatchExpression(controllerName, controllerMask);
}
// controller and action mask
return MatchExpression(controllerName, controllerMask) &&
MatchExpression(operationId, actionMask);
}
private static bool MatchExpression(string text, string expression)
{
// no mask: simple string compare
if (!expression.Contains('?') && !expression.Contains('*'))
{
return string.Equals(text, expression,
StringComparison.InvariantCultureIgnoreCase);
}
// regex
var regex = new Regex(expression.Replace(".", "
[.]").Replace("*", ".*").Replace('?', '.'));
return regex.IsMatch(text);
}
}
Applying Endpoint Filters
The filters are read from the ApiConfiguration configuration at program startup and the ApiVisibilityConvention is registered when controllers are added:
1 public class Program
2 {
3 public static void Main(string[] args)
4 {
5 var builder = WebApplication.CreateBuilder(args);
6
7 // configuration
8 IConfiguration Configuration = new ConfigurationBuilder()
9 .AddJsonFile("appsettings.json", optional: true,
reloadOnChange: true)
10 .AddEnvironmentVariables()
11 .AddCommandLine(args)
12 .Build();
13 var apiConfiguration = Configuration.GetSection(
nameof(ApiConfiguration)).Get<ApiConfiguration>();
14
15 // Add services to the container.
16 builder.Services.AddControllers(setupAction =>
17 {
18 if (apiConfiguration != null)
19 {
20 setupAction.Conventions.Add(
new ApiVisibilityConvention(
21 apiConfiguration.VisibleItems,
22 apiConfiguration.HiddenItems));
23 }
24 });
25
26 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
27 builder.Services.AddEndpointsApiExplorer();
28 builder.Services.AddSwaggerGen();
29
30 var app = builder.Build();
31
32 // Configure the HTTP request pipeline.
33 if (app.Environment.IsDevelopment())
34 {
35 app.UseSwagger();
36 // show operation id
37 app.UseSwaggerUI(setupAction =>
38 {
39 setupAction.DisplayOperationId();
40 });
41 }
42
43 app.UseHttpsRedirection();
44
45 app.UseAuthorization();
46
47
48 app.MapControllers();
49
50 app.Run();
51 }
52 }
-
8-13- load the filter configuration -
16-24- apply the visibility convention -
39- display the operation id (optional)
If no endpoint filter is active, all available endpoints are displayed in the Web UI:

Endpoints with the Include filter "VisibleItems": ["User.*", "WeatherForecast.Get*"]:

Endpoints with the Exclude filter "HiddenItems": ["User.*", "WeatherForecast.DeleteWeatherForecast"]:

Endpoints with the Exclude and Include filters "VisibleItems": ["*.Get*"], and "HiddenItems": ["User.Get*"]:

Top comments (0)