JsonValueProviderFactory:System.ArgumentException:已添加具有相同键的项目

如何解决JsonValueProviderFactory:System.ArgumentException:已添加具有相同键的项目

我有一个用于 Shopify 的 webhook api 处理程序,它使用 json 主体调用以下控制器操作。它立即失败,因为除了 OnException 方法中出现以下错误的日志之外,没有访问和记录 log4net 日志记录。

问题 1:

Elmah 日志中的堆栈跟踪没有帮助,因为它没有深入到足以显示代码中哪一行导致错误的程度。为什么是这样?我已经注意到 async 错误...它们似乎更难确定代码中的根本原因行。也许我现在应该让它成为同步方法?也许我应该摆脱 OnException 方法,因为它可能会掩盖更多信息?

问题 2:

在执行任何代码之前点击控制器操作时,什么可能会立即导致此错误?该控制器继承了 asp.net mvc Controller,构造函数中的唯一代码是创建 DBContextlog4net _logger 的实例。

堆栈跟踪:

Controllers.ShopWebhooksController.OnException(C:\inetpub\wwwroot\Controllers\ShopWebhooksController.cs:44)
System.ArgumentException: An item with the same key has already been added.
       at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
       at System.Collections.Generic.Dictionary`2.Insert(TKey key,TValue value,Boolean add)
       at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore,String prefix,Object value)
       at System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore,Object value)
       at System.Web.Mvc.JsonValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
       at System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext)
       at System.Web.Mvc.ControllerBase.get_ValueProvider()
       at System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext,ParameterDescriptor parameterDescriptor)
       at System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext,ActionDescriptor actionDescriptor)
       at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass3_1.<BeginInvokeAction>b__0(AsyncCallback asyncCallback,Object asyncState)
       at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback,Object state,Int32 timeout)
       at System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext,String actionName,AsyncCallback callback,Object state)
       at System.Web.Mvc.Controller.<>c.<BeginExecuteCore>b__152_0(AsyncCallback asyncCallback,Object asyncState,ExecuteCoreState innerState)
       at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback,Object callbackState)
       at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback,Int32 timeout)
       at System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback,Object state)
       at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback,Int32 timeout)
       at System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext,Object state)
       at System.Web.Mvc.MvcHandler.<>c.<BeginProcessRequest>b__20_0(AsyncCallback asyncCallback,ProcessRequestState innerState)
       at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback,Int32 timeout)
       at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext,Object state)
       at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
       at System.Web.HttpApplication.<>c__DisplayClass285_0.<ExecuteStepImpl>b__0()
       at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
       at System.Web.HttpApplication.ExecuteStep(IExecutionStep step,Boolean& completedSynchronously)

这是控制器,OrderUpdate 是被调用的操作:

public class ShopWebhooksController : Controller
{
private readonly ILog _logger;
private readonly InventoryMgmtContext _dbContext;

public ShopWebhooksController()
{
    _logger = LogManager.GetLogger(GetType());
    _dbContext = new InventoryMgmtContext();
}

protected override void OnException(ExceptionContext filterContext)
{
    Exception ex = filterContext.Exception;
    var action = filterContext.RouteData.Values["action"];
    // TODO: Log or report your exception.
    string msg = $"Exception in shopify webhook controller action: {action}. Message: {ex.Message}. Stack: {ex.StackTrace}.";
    _logger.Error(msg); **<---- this is being logged**

    filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.OK,msg);

    //Let the base controller finish this execution
    base.OnException(filterContext);
}      


[HttpPost]
public async Task<ActionResult> OrderUpdated (int storefrontId)
{
    string msg = "Successfully submitted update request to Mozzo.";
    string webhook = "orders/updated";
    _logger.Debug($"Shopify {webhook} request received."); **<-- not being logged**

    try
    {
        var validationResult = await ValidateStorefrontWebhook(webhook,storefrontId);
        if (!validationResult.WasSuccessful) return new HttpStatusCodeResult(HttpStatusCode.OK,validationResult.Message);

        var orderSyncAppServ = new SyncErpWithPlacedOrdersTask();
        Hangfire.BackgroundJob.Enqueue(() => orderSyncAppServ.UpdateOrderFromWebhook(validationResult.Value,storefrontId));
    }
    catch (Exception e)
    {
        msg = $"Exception webhook: {webhook} for storefront Id: {storefrontId}. {e.Message}.";
        _logger.Error(msg);
    }
    return new HttpStatusCodeResult(HttpStatusCode.OK,msg);
}

#endregion

#region Private Methods


/// <summary>
/// Validates the webhook is authentic and returns the body of the request as a string
/// </summary>
/// <param name="webhook"></param>
/// <param name="storefrontId"></param>
/// <returns>request body (string version of an order,etc.</returns>
private async Task<ActionConfirmation<string>> ValidateStorefrontWebhook(string webhook,int storefrontId)
{
    string returnMessage = "";     

    //log request
    //get the request body (a json string of an order,product,etc coming from shopify.
    string jsonObject = await GetRequestBody();

    //wrap in brackets to make it an array of one because our import takes an array or orders
    jsonObject = $"[ {jsonObject} ]";

    //get storefront
    var storefront = await _dbContext.StoreFronts.Where(s => s.Id == storefrontId).SingleOrDefaultAsync();
    if (storefront == null) {
        returnMessage = $"Shopify {webhook} webhook request for Storefront Id: {storefront.Id} - storefront not found!";
        _logger.Error($"{LogHelper.GetCurrentMethodName()}: {returnMessage}");
        return ActionConfirmation<string>.CreateFailureConfirmation(returnMessage,"",false);
    }

    log4net.LogicalThreadContext.Properties["AccountId"] = storefront.Company.AccountId;
    log4net.LogicalThreadContext.Properties["CompanyId"] = storefront.CompanyId;
    log4net.LogicalThreadContext.Properties["FacilityId"] = null;
    log4net.LogicalThreadContext.Properties["UserId"] = null;

    string shopDomain = storefront.APIUrl;
    string shopSecretKey = storefront.StoreFrontTypeId == (int)StoreFront.StoreFrontTypes.ShopifyPrivate
        ? storefront.AccessToken
        : AppSettings.ShopifySecretKey;

    _logger.Debug("About to check if webhook is authentic");

    var isValidRequest = await AuthorizationService.IsAuthenticWebhook(
        Request.Headers.ToKvps(),Request.InputStream,shopSecretKey);

    if (!isValidRequest)
    {
        returnMessage = $"Shopify {webhook} webhook request for Storefront Id: {storefront.Id} is not authentic!";
        _logger.Error($"{LogHelper.GetCurrentMethodName()}: {returnMessage}"); 
        return ActionConfirmation<string>.CreateFailureConfirmation(returnMessage,false);
    }

    returnMessage = $"Shopify {webhook} webhook request for Storefront Id: {storefront.Id} is authentic!";
    _logger.Info($"{LogHelper.GetCurrentMethodName()}: {returnMessage}");            

    return ActionConfirmation<string>.CreateSuccessConfirmation(returnMessage,jsonObject,false);
}

private async Task<string> GetRequestBody()
{
    _logger.Debug($"{LogHelper.GetCurrentMethodName()}: Attempting to get request body.");

    //ShopifySharp has just read the input stream. We must always reset the inputstream
    //before reading it again.
    Request.InputStream.Position = 0;

    //Do not dispose the StreamReader or input stream. The controller will do that itself.
    string bodyText = await new StreamReader(Request.InputStream).ReadToEndAsync();

    _logger.Debug($"{LogHelper.GetCurrentMethodName()}: Request body: {bodyText}.");

    return bodyText;
}
#endregion  

更新 - 问题和解决方案

问题确实是 Shopify Order webhook JSON 对象包含重复的键,因为它们在同一个对象包装器中有 lowercaseTitleCase 版本的 4 个键。

enter image description here

这些键的完整路径是:

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

我所做的确切代码更改如下。我确实按照下面提供的答案添加了我自己的 JsonValueProviderFactory 类,但是没有提供的是要进行的确切更改...因为这取决于您想如何处理它。就我而言,此更改会导致丢弃任何后续同名键。因此,如果您想以不同的方式处理它,则需要根据需要进行处理:

/// <summary>
/// Modified this to handle duplicate keys
/// </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.");
    }

    // Add the following if block so if the key already exists,just return instead of trying to add it to the dictionary which will throw an error.
    if (_innerDictionary.ContainsKey(key))
    {
        return;
    }

    _innerDictionary.Add(key,value);
}

解决方法

我认为您的设计没有任何问题,但是您的一个类可能具有重复的属性,这会导致运行时异常。

例如

public int storefrontId {get; set;}
public int StorefrontId {get; set;}

并且您需要配置 log4net 来记录您的操作调用。 例如:

2021-02-16 10:24:17.5632|2|INFO|Microsoft.AspNetCore.Hosting.Diagnostics|Request finished in 141.7419ms 200  |url: http://myapp/OrderUpdated|action:

编辑 以下是如何使用 DelegatingHandler

进行请求日志
public class RequestLogHandler : DelegatingHandler
{
    private static readonly ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
    {
        if (request.Content != null)
        {
            string requestBody = await request.Content.ReadAsStringAsync();
            log.Info($"url {request.RequestUri} body = {requestBody}");
        }
        //// let other handlers process the request
        var result = await base.SendAsync(request,cancellationToken);

        return result;
    }
}

在配置中注册处理程序

config.MessageHandlers.Add(new RequestLogHandler());

这会给你类似下面的东西。 enter image description here

此外,我将讲述重写 JsonValueProviderFactory AddToBackingStore 方法的步骤。您可以使用它来查找导致此问题的属性。

  1. here 获取源代码。

  2. 添加类 MyJsonValueProviderFactory.cs

  3. 在 Global.asax.cs 中的 JsonValueProviderFactoruy 之前注册您的新类

    ValueProviderFactories.Factories.Insert(0,new MyJsonValueProviderFactory());

或先删除原件并使用您的。

ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault());
ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory());

玩这个类的异常捕获,你会发现问题出在哪里,你可以从EntryLimitedDictionary类中的Add方法开始。

再次使用下面的链接来全局注册错误处理。 https://docs.microsoft.com/en-us/aspnet/web-api/overview/error-handling/exception-handling

,

我不确定我的理解是否正确,但尝试在附加方法中临时包装后台调用并使用日志记录并尝试捕获:

BackgroundJob.Enqueue(() => UpdateOrderFromWebhookWithLogging(_logger,validationResult.Value,storefrontId));

并将此方法添加到您的控制器中:

// I don't know types to write correct signature
private void UpdateOrderFromWebhookWithLogging(_logger,orderSyncAppServ,storefrontId)
        {
            try
            {
                orderSyncAppServ.UpdateOrderFromWebhook(validationResult.Value,storefrontId)
            }
            catch (Exception ex)
            {
                _logger.Error(ex);
                throw;
            }
        }

,

看起来 JsonValueProviderFactory.AddToBackingStore 正在遍历 JSON 输入并将每个叶值放入字典中。字典的键是叶节点的路径。如果遍历遇到两个具有相同路径的叶节点,则会发生该异常。

我认为您需要检查 JSON 输入数据 - 也许它有重复的键。例如。这是有效的 JSON:

{
    "id": 1,"name": "Some Name"
}

而这不是:

{
    "id": 1,"name": "Some Name","id": 2
}

因为“id”键出现不止一次。这可能会导致您看到的错误。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-