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

.NET Core WebApi中HttpGet接口如何正确绑定复杂查询参数到FromQuery模型

.NET Core GET接口复杂查询字符串绑定规范实现方案

默认[FromQuery]模型绑定无法直接解析你传递的URL编码JSON格式Filters参数,此前你实现的「转字典→转JSON→反序列化」自定义绑定器侵入性强、冗余度高,且容易跳过框架内置的模型校验、值处理管道,以下两种方案均基于.NET原生扩展点实现,符合框架设计规范:


方案1:TypeConverter实现(优先推荐,改动最小)

该方案仅需为Filters属性添加类型转换逻辑,完全复用默认模型绑定管道,性能最优、无额外副作用。

  • 第一步:实现通用JSON字符串类型转换器
using System.ComponentModel;
using System.Globalization;
using System.Text.Json;

public class JsonFilterTypeConverter<T> : TypeConverter where T : IAdvancedFilter, new()
{
    public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
    {
        if (value is not string jsonStr) return base.ConvertFrom(context, culture, value);
        if (string.IsNullOrWhiteSpace(jsonStr)) return new T();
        
        // URL解码后反序列化,大小写不敏感适配前端传参习惯
        jsonStr = Uri.UnescapeDataString(jsonStr);
        return JsonSerializer.Deserialize<T>(jsonStr, new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        });
    }
}
  • 第二步:为基类的Filters属性添加转换器标记
    修改PaginationRequest<T>基类代码:
public  class PaginationRequest<T> where T : IAdvancedFilter?, new()
{
    public int? Page { get; set; }
    public int? RowsPerPage { get; set; }
    public string? SortBy { get; set; }
    public bool Descending { set; get; }
    public string? Search { set; get; }

    [TypeConverter(typeof(JsonFilterTypeConverter<T>))]
    public T? Filters { get; set; } = new ();
}

完成以上修改后,原有Action代码不需要做任何调整,默认[FromQuery]绑定器会自动调用转换器完成Filters属性的解析,内置模型校验、空值处理逻辑全部正常生效。


方案2:全局值提供者实现(适合多接口统一规范场景)

如果项目中有大量接口需要从QueryString接收JSON格式复杂参数,可通过自定义值提供者在参数解析阶段统一处理,不需要修改任何模型类代码。

  • 实现JSON查询值提供者及工厂
public class JsonQueryValueProvider : QueryStringValueProvider
{
    private readonly IQueryCollection _queryCollection;
    public JsonQueryValueProvider(BindingSource bindingSource, IQueryCollection query, CultureInfo culture) 
        : base(bindingSource, query, culture)
    {
        _queryCollection = query;
    }

    public override ValueProviderResult GetValue(string key)
    {
        var baseResult = base.GetValue(key);
        if (baseResult != ValueProviderResult.None) return baseResult;

        if (!_queryCollection.TryGetValue(key, out var val)) return baseResult;
        var rawVal = Uri.UnescapeDataString(val.ToString()).Trim();
        if (!rawVal.StartsWith("{")) return baseResult;

        // 展平JSON对象为属性路径键值对,适配默认模型绑定逻辑
        var resultDict = new Dictionary<string, string>();
        using var doc = JsonDocument.Parse(rawVal);
        FlattenJson(doc.RootElement, key, resultDict);
        return new ValueProviderResult(new StringValues(resultDict.Values.ToArray()));
    }

    private void FlattenJson(JsonElement node, string prefix, Dictionary<string, string> output)
    {
        if (node.ValueKind == JsonValueKind.Object)
        {
            foreach (var prop in node.EnumerateObject())
            {
                FlattenJson(prop.Value, $"{prefix}.{prop.Name}", output);
            }
            return;
        }
        output[prefix] = node.ToString();
    }
}

public class JsonQueryValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        if (context.ActionContext.HttpContext.Request.Query == null) return Task.CompletedTask;
        context.ValueProviders.Insert(0, new JsonQueryValueProvider(
            BindingSource.Query,
            context.ActionContext.HttpContext.Request.Query,
            CultureInfo.InvariantCulture));
        return Task.CompletedTask;
    }
}
  • 在Program.cs中注册值提供者
builder.Services.AddControllers(options =>
{
    options.ValueProviderFactories.Insert(0, new JsonQueryValueProviderFactory());
});

该方案会自动识别所有QueryString中传递的JSON格式参数,展平为默认绑定器可识别的层级结构,支持任意层级嵌套的复杂对象绑定。


注意事项

  • 你给出的示例查询字符串中Filters的JSON存在语法错误:原始值{"ProductId":14125F3D-3662-45DC-895C-691911A4767B""}存在多余双引号、Guid类型值未加字符串引号的问题,正确的未编码JSON应为{"ProductId":"14125F3D-3662-45DC-895C-691911A4767B"},否则反序列化会失败。
  • 坚持使用GET请求实现分页查询是符合RESTful语义的合理选择,不需要为了绑定参数强制改用POST。

内容的提问来源于stack exchange,提问作者user19134022

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 23:03:54