ASP.NET Core中Swagger UI无法按Query Parameter过滤路由问题
我正在开发ASP.NET Core Web API项目,想要通过Query Parameter传入的consumer key过滤Swagger UI中显示的路由。已经实现了自定义IDocumentFilter,预期逻辑是:
- 当找不到consumer key或其值为空时,清除Swagger文档中的所有路径
- 传入正确的consumer key(如
hasad)时,仅显示带有匹配[KeyAuthorize]特性的控制器路由
但访问http://localhost:7249/swagger/index.html?consumer=hasad时,路由并未按预期过滤,日志始终提示:
HFCDDM.Api.Filters.KeyBasedDocumentFilter: Warning: Consumer key not found in query parameter or is null. No routes will be shown.
相关代码
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>();
中间件配置
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; } } }
问题根源与修复方案
1. Swagger UI未传递Query参数给swagger.json请求
Swagger UI加载swagger.json时,默认不会携带index.html页面的Query参数,导致DocumentFilter无法获取到consumer参数。
修复:
- 在Program.cs中配置Swagger UI注入自定义JS,让请求
swagger.json时带上当前页面的Query参数:app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "HFCDDM.Api v1"); c.InjectJavascript("/swagger-ui/custom.js"); }); - 在
wwwroot/swagger-ui/目录下创建custom.js文件:window.onload = function() { const ui = window.ui; const currentQuery = window.location.search; ui.getConfigs().forEach(config => { config.url += currentQuery; }); };
2. DocumentFilter中特性匹配逻辑错误
原代码会一次性获取所有控制器的KeyAuthorizeAttribute,只要有一个控制器匹配就保留所有路径,这完全不符合需求。需要针对每个路径对应的接口,单独检查其控制器/方法上的特性。
修复: 修改KeyBasedDocumentFilter.cs的Apply方法:
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { _logger.LogInformation("KeyBasedDocumentFilter is being executed."); var httpContext = _httpContextAccessor.HttpContext; string consumerKey = null; if (httpContext != null) { // 从Query参数中直接获取(swagger.json请求已带上参数) if (httpContext.Request.Query.TryGetValue("consumer", out var queryValues) && queryValues.Count > 0) { consumerKey = queryValues.First(); } } if (string.IsNullOrEmpty(consumerKey)) { _logger.LogWarning("Consumer key not found in query parameter or is null. No routes will be shown."); swaggerDoc.Paths.Clear(); return; } _logger.LogInformation($"Consumer Key: {consumerKey}"); // 遍历每个路径,检查对应的接口是否匹配consumer key foreach (var path in swaggerDoc.Paths.ToList()) { var apiDescriptions = context.ApiDescriptions.Where(api => api.RelativePath == path.Key.TrimStart('/')); bool isAuthorized = false; foreach (var apiDesc in apiDescriptions) { if (apiDesc.ActionDescriptor is ControllerActionDescriptor descriptor) { // 检查方法上的特性 var methodAttr = descriptor.MethodInfo.GetCustomAttribute<KeyAuthorizeAttribute>(); if (methodAttr != null && methodAttr.Key == consumerKey) { isAuthorized = true; break; } // 检查控制器上的特性 var controllerAttr = descriptor.ControllerTypeInfo.GetCustomAttribute<KeyAuthorizeAttribute>(); if (controllerAttr != null && controllerAttr.Key == consumerKey) { isAuthorized = true; break; } } } if (!isAuthorized) { swaggerDoc.Paths.Remove(path.Key); } } }
3. 移除重复的DocumentFilter注册
原Program.cs中同时通过c.DocumentFilter<KeyBasedDocumentFilter>()和AddScoped<IDocumentFilter, KeyBasedDocumentFilter>()注册过滤器,可能导致冲突。
修复: 删除builder.Services.AddScoped<IDocumentFilter, KeyBasedDocumentFilter>();,只保留c.DocumentFilter<KeyBasedDocumentFilter>();。
4. 确保注册IHttpContextAccessor
如果项目中未注册IHttpContextAccessor,需要在Program.cs中添加:
builder.Services.AddHttpContextAccessor();
内容的提问来源于stack exchange,提问作者rabih mh

