MVC :“已添加了具有相同键的项”
最近将一个项目从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`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.<BeginInvokeAction>b__19(AsyncCallback asyncCallback, Object asyncState)
在 System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
在 System.Web.Mvc.Async.AsyncResultWrapper.Begin[TResult](AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate`1 endDelegate, Object tag, Int32 timeout)
在 System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state)

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

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

PageIndex在赋值时写成了pageIndex(第1个字母大写P写成了小写p),在js中开头字母小写也是规范写法,当时可能是直觉性地写出来的,所以这个低级错误情有可原。
/*这时你可能不禁要问:为什么自己给自己找事,开头字母用大写呢?哎,我也有我的苦衷,这段js代码是在服务端根据C#对象的属性生成的,C#的规范是开头字母大写*/
由于这样一个低级错误,在ajax请求时发送给服务端的json字符串变成了这样:
{"PageIndex":1,"pageIndex":2}
这时找茬的劲头一涌而出,一个大大的问号浮现在眼前。。。

为什么ASP.NET MVC 3能包容这个错误,并且得到正确的值(PageIndex=2),而ASP.NET MVC 5.1却不能呢?是MVC 5.1更严谨了还是心胸更狭窄了?
好奇心的驱使下,尝试在ASP.NET MVC的开源代码中一探究竟。
- 用git签出ASP.NET MVC的源代码——https://git01.codeplex.com/aspnetwebstack
- 用VS2013打开解决方案,在解决方案管理器中搜索到JsonValueProviderFactory
在AddToBackingStore方法中找到了异常的引发点(最后1行代码 backingStore.Add(prefix, value)):

private static void AddToBackingStore(EntryLimitedDictionary backingStore, string prefix, object value)
{
IDictionary<string, object> d = value as IDictionary<string, object>;
if (d != null)
{
foreach (KeyValuePair<string, object> entry in d)
{
AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
}
return;
} IList l = value as IList;
if (l != null)
{
for (int i = 0; i < l.Count; i++)
{
AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
}
return;
} // primitive
backingStore.Add(prefix, value);
}

进一步追踪下去,找到了引发异常的具体代码行:
_innerDictionary.Add(key, value);
_innerDictionary在运行时的对应实现是:
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
在Dictionary的构造函数中特地使用了StringComparer.OrdinalIgnoreCase(也就是key不区分大小写),可见微软程序员考虑到了“大小写写错”的情况,但是没有考虑到“正确与错误的大小写都出现”的情况。
当MVC 5.1接收到 {"PageIndex":1,"pageIndex":2} 的json字符串,在执行如下操作时:
_innerDictionary.Add("PageIndex", 1);
_innerDictionary.Add("pageIndex", 2);
引爆了异常:
System.ArgumentException: 已添加了具有相同键的项。(An item with the same key has already been added)。
修复这个问题很简单:

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

是微软程序员没考虑到还是有什么特别考虑?
但是,仔细看了一下JsonValueProviderFactory的实现代码让人觉得答案更可能是前者,比如下面的代码:

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

StreadReader竟然不进行Dispose(比如放在using中),这不像是出自一个优秀程序员之手。
MVC :“已添加了具有相同键的项”的更多相关文章
- mvc已添加了具有相同键的项
异常详细信息: System.ArgumentException: 已添加了具有相同键的项. 场景重现:在地址栏输入 http://localhost:51709/Home/Index?user[0 ...
- MVC 5.1的遭遇:“已添加了具有相同键的项”
ASP.NET MVC 3升级至MVC 5.1的遭遇:“已添加了具有相同键的项” 最近将一个项目从ASP.NET MVC 3升级至刚刚发布的ASP.NET MVC 5.1,升级后发现一个ajax请 ...
- ASP.NET MVC 3升级至MVC 5.1的遭遇:“已添加了具有相同键的项”
最近将一个项目从ASP.NET MVC 3升级至刚刚发布的ASP.NET MVC 5.1,升级后发现一个ajax请求出现了500错误,日志中记录的详细异常信息如下: System.ArgumentEx ...
- System.ArgumentException: 已添加了具有相同键的项。(An item with the same key has already been added) 在 System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) 在 System.Web.Mvc.Js
最近将一个项目从ASP.NET MVC 3升级至刚刚发布的ASP.NET MVC 5.1,升级后发现一个ajax请求出现了500错误,日志中记录的详细异常信息如下: System.ArgumentEx ...
- MVC 表单提交提示:已添加了具有相同键的项。
MVC:页面提交的时候报如下错误: 解决方案: 这个Model 里面定义了重复的字段so~~~
- ArgumentException: 已添加了具有相同键的项。
此问题出现在asp.net mvc 5 中,前端向后端请求数据,方法的参数是模型,比如 Add(Student m), 结果浏览器显示的状态是500并返回错误提示ArgumentException,如 ...
- 多线程环境下非安全Dictionary引起的“已添加了具有相同键的项”问题
问题: 代码是在多线程环境下,做了简单的Key是否存的判断, 测试代码如下: public class Program { static Dictionary<string, Logger> ...
- vs2015 安装之后安装MSSM 2016 导致 vs启动报错 System.ArgumentException 已添加了具有相同键的项,ActivityLog.xml
如题,先是装了vs2015,开发什么的都没有问题,后来安装了SqlServer2016 MSSM,出大问题了,vs2015打开就报错,具体错误如上,还想还有个ActivityLog.xml 这个文件的 ...
- VS窗体选择BackGroupImage属性报错:已添加具有相同键的项
高墙我今天第一次遇见这个问题.既然说是"已添加具有相同键的项."那我自然地认为会不会是文件夹哪里命名了两个相同的文件名.然后在这个Exception上越走越远. 好了不说废话.出现 ...
随机推荐
- Java常用小笔记
1.对list集合进行分页 //startIndex指的是开始的小标 从0开始,pageSize是每页记录数 int toIndex = new Integer(startIndex)+new Int ...
- [转]jQuery 双击事件(dblclick)时,不触发单击事件(click)
例1: 链接:http://www.w3school.com.cn/jquery/event_dblclick.asp HTML 系列教程 浏览器脚本 服务器脚本 ASP.NET 教程 XML 系列教 ...
- Codeforces Round #345 (Div. 1) D. Zip-line 上升子序列 离线 离散化 线段树
D. Zip-line 题目连接: http://www.codeforces.com/contest/650/problem/D Description Vasya has decided to b ...
- (Mark)操作系统原理
Q:什么是中断?A:中断嘛,举个容易理解的例子吧. 比如莲莲和唐唐在下象棋,眼看莲莲就快招架不住了,正当此紧要关头,唐唐的女友来了,非要唐唐陪她上街买衣服不可(唐唐晕倒:-) ),于是唐唐不得不同 ...
- RequireJS 与 SeaJS 的异同
相同之处 RequireJS 和 SeaJS 都是模块加载器,倡导的是一种模块化开发理念,核心价值是让 JavaScript 的模块化开发变得更简单自然. 不同之处 两者的区别如下: 定位有差异.Re ...
- CentOS 6.9下配置安装KVM
注意:KVM一切安装和运行都是在root用户下完成的,并且只有root才能支持某些软件. 一.准备工作: 1.查看系统版本.内核版本 ##查看系统版本 # cat /etc/redhat-releas ...
- easyui combobox设置只读属性
$("#id").combobox('readonly',true); //只读 $("#id").combobox('readonly',false); // ...
- Clone()方法C#
class DrawBase:System.Object , ICloneable { public string name = "jmj"; public DrawBase() ...
- 阿里云ECS主机内核调优
#阿里云内核调优 cat /etc/sysctl.conf vm.swappiness = 0net.ipv4.neigh.default.gc_stale_time=120 # see detail ...
- MFC【17-3】线程和线程同步化
17.3小知识点 17.3.1消息泵 编写一个应用程序,让它响应某菜单命令,画几千个椭圆. void CMFC线程View::OnStartDrawing(void) { m_bQuit=FALSE; ...