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 theWeatherForecast
controller -
*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)