为何HashSet在ASP.NET Core的HttpPost中不生效,HttpGet却正常?
HashSet在HttpPost FromBody绑定中失效的原因
核心差异:两种模型绑定的执行流程不同
HashSet的重复拦截逻辑在[HttpGet] + [FromQuery]生效,但在[HttpPost] + [FromBody]失效,本质是两种绑定方式的执行流程完全不一样:
FromQuery绑定的执行逻辑
当使用FromQuery时,ASP.NET Core会逐个解析URL中的查询参数,逐个向HashSet中添加元素。每添加一个元素,HashSet都会立刻调用你的CircularStepPrizeDtoComparer进行重复校验:- 如果发现重复项,添加操作会失败,模型绑定系统会捕获到这个状态变更,直接将错误写入
ModelState,最终返回模型验证错误。 - 这个过程是增量式校验,每一步添加都触发规则,所以重复项会被即时拦截。
- 如果发现重复项,添加操作会失败,模型绑定系统会捕获到这个状态变更,直接将错误写入
FromBody绑定的执行逻辑(以JSON序列化为例)
当使用FromBody时,序列化框架(如System.Text.Json、Newtonsoft.Json)的处理流程是:- 先将整个JSON数组反序列化为一个临时集合(默认是List
) ,这个过程完全不感知你定义的HashSet和自定义Comparer。 - 反序列化完成后,再将临时集合中的所有元素批量复制到HashSet中。
- 此时HashSet确实会自动忽略重复项,但这个批量添加的过程是在模型绑定完成后执行的,ASP.NET Core的模型绑定系统不会监听HashSet内部的添加结果,自然不会把重复项的问题写入
ModelState。
简单说:FromBody是先完成反序列化,再赋值给HashSet,中间的重复拦截不会被绑定系统捕获。
- 先将整个JSON数组反序列化为一个临时集合(默认是List
关于List包装HashSet方案的说明
用List包装HashSet的方案能去重,但不会返回ModelState错误,原因是:
- 反序列化先把JSON转成List,之后你通过自定义逻辑(比如构造函数、属性设置器)将List转为HashSet去重,这个过程是你自己的业务逻辑,没有和ASP.NET Core的模型验证系统挂钩,所以只会自动去重,不会生成验证错误信息。
如何在FromBody时返回重复项的ModelState错误
如果需要在FromBody绑定也返回重复项的验证错误,不能依赖HashSet的自动校验,需要手动添加验证逻辑:
- 让
InsertCircularStepDto实现IValidatableObject接口 - 在
Validate方法中手动检查集合中的ProductModelId和ProductModelUnitId是否重复,若重复则向ValidationContext添加错误
示例代码:
public class InsertCircularStepDto : IValidatableObject { public HashSet<CircularStepPrizeDto> Prizes { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { var grouped = Prizes.GroupBy(p => new { p.ProductModelId, p.ProductModelUnitId }); foreach (var group in grouped.Where(g => g.Count() > 1)) { yield return new ValidationResult( $"存在重复的奖品记录:ProductModelId={group.Key.ProductModelId}, ProductModelUnitId={group.Key.ProductModelUnitId}", new[] { nameof(Prizes) }); } } }
内容的提问来源于stack exchange,提问作者Milad Ahmadi
相关产品推荐
相关产品推荐

