You need to enable JavaScript to run this app. ASP.NET MVC接收Shopify Webhook时JsonValueProviderFactory报重复键异常
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET MVC接收Shopify Webhook时JsonValueProviderFactory报重复键异常

问题1:异步方法堆栈无具体业务行的原因
  • 本次异常和异步方法的堆栈特性无关。你拿到的堆栈链路里,根本没有进入到OrderUpdated这个Action方法的栈帧中,自然不可能出现业务代码的行号。
  • .NET的async/await状态机确实会在跨上下文恢复时裁剪堆栈信息,导致异常堆栈不像同步代码那样能完整追踪到每一行代码,但这不是这次堆栈简略的原因——异常在框架的模型绑定阶段就抛出了,压根没走到你写的异步逻辑。完全不需要为了定位问题临时把异步方法改成同步实现。
  • 你重写的OnException方法不会遮挡异常的原始细节。它只是作为MVC的异常过滤点,捕获框架向上抛出的异常、打印日志、设置响应结果,你现在拿到的堆栈就是从异常抛出点到捕获点的完整链路,不存在信息被拦截丢失的问题。你观察到Action内部日志不执行,也不是因为OnException的拦截逻辑,是代码执行流程根本没进入Action方法体。
问题2:异常早于Action业务代码触发的原因
  • 梳理完整堆栈可以发现,异常的触发链路是:MVC框架在激活控制器后、正式调用Action之前,会先执行模型绑定流程,准备好Action方法需要的所有参数值。
  • 这个流程里,JsonValueProviderFactory会负责解析请求携带的JSON Body,将所有JSON节点转换成键值对存入值提供器,供参数绑定逻辑取用。
  • 你的Action定义了int storefrontId参数,框架在从值提供器里读取这个参数值的时候,就同步触发了JSON解析阶段的字典插入冲突,整个过程发生在Action方法体执行之前,所以你写在Action第一行的log4net日志永远不会被执行到。
  • 你Controller的构造函数逻辑(初始化DBContext实例、log4net日志对象)是控制器激活阶段就执行完成的,和本次异常没有关联,不需要调整构造函数的实现。
根因定位

结合堆栈信息和实际排查结果,本次异常的触发根源是Shopify推送的Webhook JSON结构中存在重复键:在order.refunds[0].transactions[0].receipt节点下,共检测到4组重复键,完整路径为:

  • order,refunds,0,transactions,0,receipt,version
  • order,refunds,0,transactions,0,receipt,timestamp
  • order,refunds,0,transactions,0,receipt,ack
  • order,refunds,0,transactions,0,receipt,build

框架自带的JsonValueProviderFactory在处理嵌套JSON时,会把节点路径拼接成统一格式的键名(例如order.refunds[0].transactions[0].receipt.version),当出现大小写不同但拼接后键名完全一致的节点时,往内部字典插入值就会抛出"An item with the same key has already been added"的ArgumentException。

解决方案
  • 替换ASP.NET MVC框架默认的JsonValueProviderFactory,重写其内部用于存储键值的EntryLimitedDictionary类的Add方法,针对重复键场景做容错:检测到待添加的键已存在时直接返回,不再尝试插入重复键触发异常。
  • 修改后的核心代码如下:
/// <summary>
/// 适配重复键场景的字典Add逻辑
/// </summary>
/// <param name="key">值提供器存储的键</param>
/// <param name="value">键对应的值</param>
public void Add(string key, object value)
{
    if (++_itemCount > _maximumDepth)
    {
        throw new InvalidOperationException("The JSON request was too large to be deserialized.");
    }

    // 键已存在时直接跳过,避免重复插入抛出异常
    if (_innerDictionary.ContainsKey(key))
    {
        return;
    }

    _innerDictionary.Add(key, value);
}
  • 这种容错逻辑会保留JSON中最先出现的同键值,后续重复的键值会被直接忽略,不会影响Action参数的正常取值(storefrontId这类从路由、查询字符串读取的参数,和解析请求体的JSON值提供器链路完全隔离)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.04 03:54:03