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上越走越远. 好了不说废话.出现 ...
随机推荐
- (转载)JavaScript中匿名函数,函数直接量和闭包
首先,我们先看看下面几种写法:1.function f(x){return x*x;};f(x);2.(function(x){return x*x;})(x);3.(function(x){retu ...
- JS的scrollIntoView学习
scrollIntoView(alignWithTop) 滚动浏览器窗口或容器元素,以便在当前视窗的可见范围看见当前元素.如果alignWithTop为true,或者省略它,窗口会尽可能滚动到自身顶 ...
- 【shiro】报错: If the controller requires proxying (e.g. due to @Transactional), please use class-based proxying.
spring整合shiro,项目报如下错误: ==============异常开始============= java.lang.IllegalStateException: The mapped c ...
- 【POI】解析xls报错:java.util.zip.ZipException: error in opening zip file
今天使用POI解析XLS,报错如下: Servlet.service() for servlet [rest] in context with path [/cetBrand] threw excep ...
- Jenkins环境初步配置
为研究在kubernetes上的CICD,先在物理环境下安装个JenKins热热身. 安装Jenkins 在官网https://jenkins.io/下载war包,我的是http://mirrors. ...
- 智普教育Python视频教程之入门基础篇,python笔记
智普教育Python视频教程之入门基础篇,python笔记 print id()内存地址 type()变量类型 windows命令行下edit命令 python数据类型不需要指定类型 定义hostna ...
- 如何在windows下的Python开发工具IDLE里安装其他模块
以安装Httplib2模块为例 1 下载模块 到 “https://code.google.com/p/httplib2/” 下载一款适合你的压缩包“httplib2-0.4.0.zip” 2 解压下 ...
- jQuery.toggleClass() 和detach()方法详解
一.toggleClass()函数: toggleClass()函数用于切换当前jQuery对象所匹配的每一个元素上指定的css类名.所谓"切换",就是如果该元素上已存在指定的类名 ...
- Bayesian statistics
文件夹 1Bayesian model selection贝叶斯模型选择 1奥卡姆剃刀Occams razor原理 2Computing the marginal likelihood evidenc ...
- linux 的计划任务 cron
https://serverfault.com/questions/587696/how-to-restart-php-fpm-from-cron 我也遇到了这个问题,想用cron 来启动php-fp ...