I’m working on an ASP.NET Core Web API project and I’m trying to filter the routes displayed in Swagger UI based on a consumer key provided as a query parameter. I’ve implemented a custom IDocumentFilter
that should remove all paths from the Swagger document if the consumer key is not found or is null. However, when I navigate to http://localhost:7249/swagger/index.html?consumer=hasad, it’s not filtering the routes as expected.
Here’s the relevant part of my code:
// KeyBasedDocumentFilter.cs
namespace HFCDDM.Api.Filters
{
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Linq;
using System.Reflection;
public class KeyBasedDocumentFilter : IDocumentFilter
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<KeyBasedDocumentFilter> _logger;
public KeyBasedDocumentFilter(IHttpContextAccessor httpContextAccessor, ILogger<KeyBasedDocumentFilter> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
_logger.LogInformation("KeyBasedDocumentFilter is being executed.");
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null || !httpContext.Request.Query.TryGetValue("consumer", out var consumerKeys) || consumerKeys.Count == 0)
{
_logger.LogWarning("Consumer key not found in query parameter or is null. No routes will be shown.");
swaggerDoc.Paths.Clear(); // Clear all paths if no valid consumer key is found
return;
}
var consumerKey = consumerKeys.First();
_logger.LogInformation($"Consumer Key: {consumerKey}");
foreach (var path in swaggerDoc.Paths.ToList())
{
var pathItem = path.Value;
var controllerAttributes = context.ApiDescriptions
.Select(apiDesc => apiDesc.ActionDescriptor.EndpointMetadata)
.OfType<ControllerActionDescriptor>()
.SelectMany(descriptor => descriptor.ControllerTypeInfo.GetCustomAttributes<KeyAuthorizeAttribute>())
.ToList();
// If no controller has the KeyAuthorizeAttribute or the consumer key doesn't match, remove the path
if (!controllerAttributes.Any() || !controllerAttributes.Any(attr => attr.Key == consumerKey))
{
swaggerDoc.Paths.Remove(path.Key);
}
}
}
}
}
// Program.cs
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "HFCDDM.Api", Version = "v1" });
c.DocumentFilter<KeyBasedDocumentFilter>();
});
builder.Services.AddScoped<IDocumentFilter, KeyBasedDocumentFilter>();
// Middleware configuration
app.Use(async (context, next) =>
{
if (context.Request.Path.StartsWithSegments("/swagger/index.html"))
{
var consumerKey = context.Request.Query["consumer"].FirstOrDefault();
if (!string.IsNullOrEmpty(consumerKey))
{
// Pass the consumer key to the document filter
context.Items["ConsumerKey"] = consumerKey;
}
}
await next();
});
// AccountController.cs
[Route("api/[controller]")]
[ApiController]
[Authorize]
[KeyAuthorize("hasad")]
public class AccountController : ControllerBase
{
// ... (controller actions)
}
// KeyAuthorizeAttribute.cs
namespace HFCDDM.Api
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class KeyAuthorizeAttribute : Attribute
{
public string Key { get; }
public KeyAuthorizeAttribute(string key)
{
Key = key;
}
}
}
The KeyBasedDocumentFilter
should clear all paths if the consumer key is not present or is null, but it seems like the consumer key is not being recognized correctly. The KeyAuthorize
attribute is applied to the AccountController
, and I expect only this controller’s routes to be shown when the correct key is provided.
What I Expect: When I access the Swagger UI without a consumer key or with a null key, I expect no routes to be shown. When I provide the correct consumer key (hasad) in the query parameter, I expect only the routes from AccountController
to be visible. However, the logs indicate that the consumer key is not being found:
HFCDDM.Api.Filters.KeyBasedDocumentFilter: Warning: Consumer key not found in query parameter.
Here is a whole working demo you could follow:
public class KeyBasedDocumentFilter : IDocumentFilter
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<KeyBasedDocumentFilter> _logger;
public KeyBasedDocumentFilter(IHttpContextAccessor httpContextAccessor, ILogger<KeyBasedDocumentFilter> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
_logger.LogInformation("KeyBasedDocumentFilter is being executed.");
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null || !httpContext.Request.Query.TryGetValue("consumer", out var consumerKeys) || consumerKeys.Count == 0)
{
_logger.LogWarning("Consumer key not found in query parameter or is null. No routes will be shown.");
swaggerDoc.Paths.Clear(); // Clear all paths if no valid consumer key is found
return;
}
var consumerKey = consumerKeys.First();
_logger.LogInformation($"Consumer Key: {consumerKey}");
var controllerTypes = Assembly.GetExecutingAssembly().GetTypes().Where(type => typeof(ControllerBase).IsAssignableFrom(type));
// Check each controller type for KeyAuthorizeAttribute
foreach (var controllerType in controllerTypes)
{
var hasKeyAuthorizeAttribute = controllerType.GetCustomAttributes<KeyAuthorizeAttribute>().Any(attr => attr.Key == consumerKey);
if (!hasKeyAuthorizeAttribute)
{
var controllerTypeName = controllerType.Name;
var controllerNameWithoutSuffix = controllerTypeName.EndsWith("Controller") ? controllerTypeName.Substring(0, controllerTypeName.Length - "Controller".Length) : controllerTypeName;
// Remove paths associated with this controller
var pathsToRemove = swaggerDoc.Paths.Keys.Where(path => path.Contains($"/{controllerNameWithoutSuffix}")).ToList();
foreach (var pathToRemove in pathsToRemove)
{
swaggerDoc.Paths.Remove(pathToRemove);
}
}
}
}
}
And no need use the Middleware configuration like what you did.