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

如何让Swagger UI显示GET请求中复杂Record的查询参数

问题描述

我在ASP.NET Core的GET接口中,使用自定义绑定的Record SearchProductsRequest 接收查询字符串参数,示例URL如下:
/v1/products?ids=1,2,3&name=hombre&page=3&pageItems=4&sortField=name&sort=asc

接口核心代码:

app.MapGet(
    $"/{ProductCatalogueApi.Version}/products",
    (SearchProductsRequest request)
         => ProductApiDelegates.SearchProducts(request));

我已经在SearchProductsRequest中实现了BindAsync方法,能正常将URL参数转换为Record实例,但Swagger UI无法识别该Record的成员,无法生成对应的参数输入框。目前只有把所有参数显式写在MapGet方法参数中,Swagger才能正常展示参数。

SearchProductsRequest完整定义:

public record SearchProductsRequest
{
    public IEnumerable<int>? Ids { get; private set; }
    public string? Name { get; private set; }
    public PaginationInfoRequest? PaginationInfo { get; private set; }
    public SortingInfoRequest? SortingInfo { get; private set; }

    public SearchProductsRequest(
        IEnumerable<int>? ids,
        string? name,
        PaginationInfoRequest? PaginationInfo,
        SortingInfoRequest? SortingInfo)
    {
        this.Ids = ids;
        this.Name = name;
        this.PaginationInfo = PaginationInfo;
        this.SortingInfo = SortingInfo;
    }

    public static ValueTask<SearchProductsRequest?> BindAsync(
        HttpContext httpContext,
        ParameterInfo parameter)
    {
        var ids = ParseIds(httpContext);
        var name = httpContext?.Request.Query["name"] ?? string.Empty;

        PaginationInfoRequest? pagination = null;
        SortingInfoRequest? sorting = null;

        if (int.TryParse(httpContext?.Request.Query["page"], out var page)
            && int.TryParse(httpContext?.Request.Query["pageItems"], out var pageItems))
        {
            pagination = new PaginationInfoRequest(page, pageItems);
        }

        var sortField = httpContext?.Request.Query["sortField"].ToString();
        if (!string.IsNullOrEmpty(sortField))
        {
            sorting = new SortingInfoRequest(
                sortField,
                httpContext?.Request.Query["sort"].ToString() == "asc");
        }

        return ValueTask.FromResult<SearchProductsRequest?>(
            new SearchProductsRequest(
                ids,
                name!,
                pagination,
                sorting));
    }

    private static int[]? ParseIds(HttpContext httpContext)
    {
        int[]? ids = null;
        var commaSeparatedIds = httpContext?.Request.Query["ids"].ToString();

        if (!string.IsNullOrEmpty(commaSeparatedIds))
        {
            ids = commaSeparatedIds
                .Split(",")
                .Select(int.Parse)
                .ToArray() ?? Array.Empty<int>();
        }

        return ids;
    }
}
解决方案

要让Swagger UI识别自定义绑定的Record成员,需要创建自定义参数过滤器,手动将Record的属性(包括嵌套对象的属性)映射为Swagger的查询参数。

步骤1:创建自定义参数过滤器

创建一个实现IParameterFilter的类,针对SearchProductsRequest的结构,将其成员转换为对应的Swagger查询参数:

using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.AspNetCore.Mvc.ApiExplorer;

public class SearchProductsRequestParameterFilter : IParameterFilter
{
    public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
    {
        // 仅处理SearchProductsRequest类型的参数
        if (context.ParameterInfo.ParameterType != typeof(SearchProductsRequest))
            return;

        // 清空Swagger默认生成的单一request参数
        context.ApiDescription.ParameterDescriptions.Clear();

        // 逐个添加查询参数
        AddQueryParameter(context, "ids", typeof(IEnumerable<int>), "逗号分隔的产品ID列表", false);
        AddQueryParameter(context, "name", typeof(string), "产品名称关键词", false);
        AddQueryParameter(context, "page", typeof(int), "页码", false);
        AddQueryParameter(context, "pageItems", typeof(int), "每页条数", false);
        AddQueryParameter(context, "sortField", typeof(string), "排序字段", false);
        AddQueryParameter(context, "sort", typeof(string), "排序方向:asc/desc", false);
    }

    private void AddQueryParameter(ParameterFilterContext context, string paramName, Type paramType, string description, bool isRequired)
    {
        var paramDesc = new ApiParameterDescription
        {
            Name = paramName,
            Type = paramType,
            Source = Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource.Query,
            IsRequired = isRequired,
            Description = description
        };

        context.ApiDescription.ParameterDescriptions.Add(paramDesc);
    }
}

步骤2:注册过滤器到Swagger配置

在Program.cs的Swagger配置中,添加这个自定义过滤器:

builder.Services.AddSwaggerGen(c =>
{
    // 其他Swagger配置(如文档标题、版本等)
    c.ParameterFilter<SearchProductsRequestParameterFilter>();
});

可选:通用嵌套对象处理(扩展方案)

如果需要支持任意带自定义绑定的Record类型,可以实现通用过滤器,通过反射自动解析嵌套属性:

using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using System.Reflection;

public class GenericRecordParameterFilter : IParameterFilter
{
    public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
    {
        var paramType = context.ParameterInfo.ParameterType;
        // 仅处理带BindAsync静态方法的Record类型
        if (!paramType.IsRecord() || !HasBindAsyncMethod(paramType))
            return;

        context.ApiDescription.ParameterDescriptions.Clear();
        // 递归解析所有属性(包括嵌套对象)
        ParseProperties(context, paramType, string.Empty);
    }

    private void ParseProperties(ParameterFilterContext context, Type type, string parentPrefix)
    {
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            var propType = prop.PropertyType;
            var paramKey = string.IsNullOrEmpty(parentPrefix) ? prop.Name : $"{parentPrefix}.{prop.Name}";

            // 处理嵌套复杂类型
            if (!propType.IsValueType && propType != typeof(string) && !propType.IsEnum)
            {
                ParseProperties(context, propType, paramKey);
                continue;
            }

            // 映射为URL中的查询参数名(和BindAsync逻辑对应)
            var queryParamName = paramKey switch
            {
                "PaginationInfo.Page" => "page",
                "PaginationInfo.PageItems" => "pageItems",
                "SortingInfo.SortField" => "sortField",
                "SortingInfo.IsAscending" => "sort",
                _ => char.ToLowerInvariant(paramKey[0]) + paramKey.Substring(1) // 默认小驼峰转换
            };

            AddQueryParameter(context, queryParamName, propType, prop.Name, false);
        }
    }

    private void AddQueryParameter(ParameterFilterContext context, string paramName, Type paramType, string description, bool isRequired)
    {
        var paramDesc = new ApiParameterDescription
        {
            Name = paramName,
            Type = paramType,
            Source = Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource.Query,
            IsRequired = isRequired,
            Description = description
        };

        context.ApiDescription.ParameterDescriptions.Add(paramDesc);
    }

    private bool HasBindAsyncMethod(Type type)
    {
        return type.GetMethod("BindAsync", BindingFlags.Public | BindingFlags.Static) != null;
    }
}

注册通用过滤器:

builder.Services.AddSwaggerGen(c =>
{
    c.ParameterFilter<GenericRecordParameterFilter>();
});
效果验证

启动项目后,Swagger UI会显示所有对应的查询参数输入框,和显式声明参数的效果一致,用户可直接在Swagger中输入参数并调用接口。

内容的提问来源于stack exchange,提问作者Daniel Mendonça

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 22:11:02