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