You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core中Swagger UI无法按Query Parameter过滤路由问题

问题:通过Query Parameter过滤Swagger UI路由失败

我正在开发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 15:54:52