热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

MVC:“已添加了具有相同键的项”

最近将一个项目从ASP.NETMVC3升级至刚刚发布的ASP.NETMVC5.1,升级后发现一个ajax请求出现了500错误,日志中记录的详细异常信息如

最近将一个项目从ASP.NET MVC 3升级至刚刚发布的ASP.NET MVC 5.1,升级后发现一个ajax请求出现了500错误,日志中记录的详细异常信息如下:

System.ArgumentException: 已添加了具有相同键的项。(An item with the same key has already been added)在 System.Collections.Generic.Dictionary&#96;2.Insert(TKey key, TValue value, Boolean add)在 System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)在 System.Web.Mvc.JsonValueProviderFactory.AddToBackingStore(EntryLimitedDictionary backingStore, String prefix, Object value)在 System.Web.Mvc.JsonValueProviderFactory.GetValueProvider(ControllerContext controllerContext)在 System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext)在 System.Web.Mvc.ControllerBase.get_ValueProvider()在 System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)在 System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)在 System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.b__19(AsyncCallback asyncCallback, Object asyncState)在 System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase&#96;1.Begin(AsyncCallback callback, Object state, Int32 timeout)在 System.Web.Mvc.Async.AsyncResultWrapper.Begin[TResult](AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate&#96;1 endDelegate, Object tag, Int32 timeout)在 System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state)

虽然问题是由于升级至MVC 5.1引起的&#xff0c;但本着“遇到问题&#xff0c;先怀疑自己”的原则&#xff0c;检查了一下代码&#xff0c;竟然在js代码中发现了一个存在已久的低级错误&#xff1a;

var pagingBuider &#61; { "PageIndex": 1 };
function buildPaging(pageIndex) {pagingBuider.pageIndex &#61; pageIndex;$.ajax({data: JSON.stringify(pagingBuider),contentType: &#39;application/json; charset&#61;utf-8&#39;});
}

PageIndex在赋值时写成了pageIndex&#xff08;第1个字母大写P写成了小写p&#xff09;&#xff0c;在js中开头字母小写也是规范写法&#xff0c;当时可能是直觉性地写出来的&#xff0c;所以这个低级错误情有可原。

/*这时你可能不禁要问&#xff1a;为什么自己给自己找事&#xff0c;开头字母用大写呢&#xff1f;哎&#xff0c;我也有我的苦衷&#xff0c;这段js代码是在服务端根据C#对象的属性生成的&#xff0c;C#的规范是开头字母大写*/

由于这样一个低级错误&#xff0c;在ajax请求时发送给服务端的json字符串变成了这样&#xff1a;

{"PageIndex":1,"pageIndex":2}

这时找茬的劲头一涌而出&#xff0c;一个大大的问号浮现在眼前。。。

 

为什么ASP.NET MVC 3能包容这个错误&#xff0c;并且得到正确的值&#xff08;PageIndex&#61;2&#xff09;&#xff0c;而ASP.NET MVC 5.1却不能呢&#xff1f;是MVC 5.1更严谨了还是心胸更狭窄了&#xff1f;

好奇心的驱使下&#xff0c;尝试在ASP.NET MVC的开源代码中一探究竟。

  • 用git签出ASP.NET MVC的源代码——https://git01.codeplex.com/aspnetwebstack
  • 用VS2013打开解决方案&#xff0c;在解决方案管理器中搜索到JsonValueProviderFactory

在AddToBackingStore方法中找到了异常的引发点&#xff08;最后1行代码 backingStore.Add(prefix, value)&#xff09;&#xff1a;

private static void AddToBackingStore(EntryLimitedDictionary backingStore, string prefix, object value)
{IDictionary d &#61; value as IDictionary;if (d !&#61; null){foreach (KeyValuePair entry in d){AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);}return;}IList l &#61; value as IList;if (l !&#61; null){for (int i &#61; 0; i backingStore.Add(prefix, value);
}

进一步追踪下去&#xff0c;找到了引发异常的具体代码行&#xff1a;

_innerDictionary.Add(key, value);

_innerDictionary在运行时的对应实现是&#xff1a;

new Dictionary(StringComparer.OrdinalIgnoreCase);

在Dictionary的构造函数中特地使用了StringComparer.OrdinalIgnoreCase&#xff08;也就是key不区分大小写&#xff09;&#xff0c;可见微软程序员考虑到了“大小写写错”的情况&#xff0c;但是没有考虑到“正确与错误的大小写都出现”的情况。

当MVC 5.1接收到 {"PageIndex":1,"pageIndex":2} 的json字符串&#xff0c;在执行如下操作时&#xff1a;

_innerDictionary.Add("PageIndex", 1);
_innerDictionary.Add("pageIndex", 2);

引爆了异常&#xff1a;

System.ArgumentException: 已添加了具有相同键的项。(An item with the same key has already been added)。

修复这个问题很简单&#xff1a;

if (_innerDictionary.ContainsKey(key))
{_innerDictionary[key] &#61; value;
}
else
{_innerDictionary.Add(key, value);
}

是微软程序员没考虑到还是有什么特别考虑&#xff1f;

但是&#xff0c;仔细看了一下JsonValueProviderFactory的实现代码让人觉得答案更可能是前者&#xff0c;比如下面的代码&#xff1a;

private static object GetDeserializedObject(ControllerContext controllerContext)
{if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)){// not JSON requestreturn null;}StreamReader reader &#61; new StreamReader(controllerContext.HttpContext.Request.InputStream);string bodyText &#61; reader.ReadToEnd();if (String.IsNullOrEmpty(bodyText)){// no JSON datareturn null;}JavascriptSerializer serializer &#61; new JavascriptSerializer();object jsonData &#61; serializer.DeserializeObject(bodyText);return jsonData;
}

StreadReader竟然不进行Dispose&#xff08;比如放在using中&#xff09;&#xff0c;这不像是出自一个优秀程序员之手。



推荐阅读
author-avatar
louis_bana
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有