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

ASP.NET Core6兼容旧Web API2 List模型绑定实现方法

问题说明

将遗留ASP.NET Web API 2项目迁移至ASP.NET Core 6时,HTTP GET接口的列表类型查询参数绑定失效:
入参类定义如下:

public class GetUsersArgs
{
    public List<int> UserIds { get; set; }
}

旧客户端固定使用api/Users/GetUsers?UserIds[]=1&UserIds[]=5&UserIds[]=10格式发起调用,该格式在Web API 2中可正常绑定为3个元素的列表,但ASP.NET Core 6默认模型绑定规则无法识别带[]后缀的集合参数名,且客户端代码不可修改。

实现方案

ASP.NET Core默认集合绑定仅识别同名多值参数(即?UserIds=1&UserIds=5&UserIds=10格式),要兼容旧版带方括号后缀的参数格式,可选择以下两种实现方式:

方案1:自定义全局模型绑定器(推荐)

一次配置即可让所有接口的List<T>类型查询参数同时兼容带[]后缀和不带后缀的两种格式,对业务代码无侵入。

  1. 实现自定义列表模型绑定器,逻辑中同时匹配两种格式的参数名:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;

public class BracketSuffixListModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        var modelType = bindingContext.ModelType;
        // 仅处理List<T>类型的绑定
        if (!modelType.IsGenericType || modelType.GetGenericTypeDefinition() != typeof(List<>))
            return Task.CompletedTask;

        var elementType = modelType.GetGenericArguments()[0];
        var paramName = bindingContext.ModelName;
        // 优先匹配无后缀参数名,未命中则尝试匹配带[]后缀的参数名
        var valueResult = bindingContext.ValueProvider.GetValue(paramName);
        if (valueResult == ValueProviderResult.None)
            valueResult = bindingContext.ValueProvider.GetValue($"{paramName}[]");

        if (valueResult == ValueProviderResult.None)
            return Task.CompletedTask;

        bindingContext.ModelState.SetModelValue(paramName, valueResult);
        try
        {
            var listInstance = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
            foreach (var rawValue in valueResult)
            {
                if (string.IsNullOrWhiteSpace(rawValue)) continue;
                listInstance.Add(Convert.ChangeType(rawValue, elementType));
            }
            bindingContext.Result = ModelBindingResult.Success(listInstance);
        }
        catch (Exception ex)
        {
            bindingContext.ModelState.TryAddModelError(paramName, $"参数值转换失败:{ex.Message}");
        }
        return Task.CompletedTask;
    }
}
  1. 实现模型绑定提供器,用于将上述绑定器匹配到List类型的模型:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;

public class BracketSuffixListModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        if (context.Metadata.ModelType.IsGenericType && 
            context.Metadata.ModelType.GetGenericTypeDefinition() == typeof(List<>))
        {
            return new BinderTypeModelBinder(typeof(BracketSuffixListModelBinder));
        }
        return null;
    }
}
  1. 在Program.cs中注册绑定提供器,注意要插入到默认集合绑定提供器之前,保证优先执行:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    // 插入到绑定提供器列表首位,优先处理List类型参数
    options.ModelBinderProviders.Insert(0, new BracketSuffixListModelBinderProvider());
});

// 其余服务注册、中间件配置按原有逻辑编写
var app = builder.Build();
app.Run();

方案2:单属性指定参数别名

如果仅少量接口需要兼容旧格式,可直接在入参类的对应属性上,通过FromQuery特性显式指定带[]后缀的参数名:

public class GetUsersArgs
{
    [FromQuery(Name = "UserIds[]")]
    public List<int> UserIds { get; set; }
}

该方案实现简单,但每个需要兼容的属性都要手动加特性,且默认无法同时兼容不带[]后缀的新调用格式,适合接口量极少的场景使用。

注意:不建议通过重写默认查询字符串值提供器的方式做全局参数名替换,该方式会影响所有查询参数的匹配逻辑,容易引发非集合类型的绑定异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:31:06