3. 模型绑定与复杂类型参数

模型绑定与复杂类型参数 学习卡片

复杂类型参数的绑定路径

默认模型绑定器对复杂类型参数的处理规则是:按属性名逐层匹配。对于查询字符串和表单数据,绑定器会先查找与参数名同名的前缀,再按 属性名 或 前缀.属性名 的路径取值。嵌套对象用点号分隔,集合用 [索引] 或同名重复键表示。

查询字符串 ?name=张三&age=28 &address.city=北京 &address.zip=100000 &tags[0]=VIP &tags[1]=新客 表单数据 name=李四 age=35 address.city=上海 address.zip=200000 tags[0]=老客户 tags[1]=批发 默认模型绑定器 按属性名逐层匹配 点号 → 嵌套对象 [索引] → 集合元素 同名重复键 → 集合 绑定结果对象 CustomerDto Name / Age Address (嵌套) City / Zip address.city / address.zip Tags (集合) List<string> tags[0] / tags[1] 绑定失败场景 缺少前缀 → 属性为默认值 类型不匹配 → 绑定错误 集合索引跳跃 → 空元素 查询字符串与表单数据按属性名路径逐层匹配,嵌套用点号,集合用索引

定义接收复杂类型的 DTO

先建立与请求数据结构对应的模型类。嵌套对象和集合都作为属性声明,绑定器会自动递归处理。

public class AddressDto
{
    public string City { get; set; } = "";
    public string Zip { get; set; } = "";
}

public class CustomerDto
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
    public AddressDto Address { get; set; } = new();
    public List<string> Tags { get; set; } = new();
}

动作方法接收与验证

在控制器动作中直接声明 CustomerDto 参数,ASP.NET Core 会从查询字符串和表单中自动绑定。通过 ModelState 检查绑定是否成功,并输出实际绑定结果。

[ApiController]
[Route("api/[controller]")]
public class CustomerController : ControllerBase
{
    [HttpPost("create")]
    public IActionResult Create([FromQuery] CustomerDto queryDto,
                                [FromForm] CustomerDto formDto)
    {
        // 分别验证两个来源的绑定结果
        var queryValid = ModelState.IsValid;
        var formValid = ModelState.IsValid;

        return Ok(new
        {
            QuerySource = new
            {
                queryDto.Name,
                queryDto.Age,
                queryDto.Address.City,
                queryDto.Address.Zip,
                queryDto.Tags
            },
            FormSource = new
            {
                formDto.Name,
                formDto.Age,
                formDto.Address.City,
                formDto.Address.Zip,
                formDto.Tags
            },
            ModelStateErrors = ModelState
                .Where(kv => kv.Value?.Errors.Count > 0)
                .Select(kv => new { kv.Key, Errors = kv.Value!.Errors.Select(e => e.ErrorMessage) })
        });
    }
}
绑定来源优先级:当同一参数同时出现在查询字符串和表单中时,[FromQuery] 和 [FromForm] 显式指定来源可避免歧义。若不加特性,默认按 Form → Route → Query 的顺序查找。

验证绑定结果

启动应用后,用以下请求分别测试查询字符串和表单数据的绑定效果。

# 测试查询字符串绑定
curl -X POST "http://localhost:5000/api/customer/create?name=张三&age=28&address.city=北京&address.zip=100000&tags[0]=VIP&tags[1]=新客"

# 测试表单数据绑定
curl -X POST "http://localhost:5000/api/customer/create" \
  -d "name=李四" \
  -d "age=35" \
  -d "address.city=上海" \
  -d "address.zip=200000" \
  -d "tags[0]=老客户" \
  -d "tags[1]=批发"

预期返回的 JSON 中,QuerySource 和 FormSource 应分别包含对应来源的完整数据。若某个属性未出现在请求中,该属性保持默认值(字符串为空、数值为 0、集合为空列表),ModelState 不会报错——这是默认绑定器与验证特性的关键区别。

常见绑定失败:集合索引跳跃(如只传 tags[0] 和 tags[2])会导致 tags[1] 为 null;嵌套对象缺少前缀(如只传 city=北京 而非 address.city=北京)则 Address.City 不会被赋值。