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上越走越远. 好了不说废话.出现 ...
随机推荐
- 阿里云ECS使用SSH连接CentOS 6.9经常断线的问题解决:OperationTimedOut
说明:不一定有效,可以试一下. 设置: vi /etc/ssh/sshd_config #添加或修改以下配置 ClientAliveInterval #每隔多少秒给SSH客户端发送一次信号 Clien ...
- thinkphp去重统计数据sql
DISTINCT 方法用于返回唯一不同的值 官方文档给出的示例: $Model->distinct(true)->field('userName')->select(); 解析的SQ ...
- node.js创建并引用模块
app.js var express = require('express'); var app = express(); var con = require('./content'); con.he ...
- [Android Pro] 创建快捷方式,删除快捷方式,查询是否存在快捷方式
1: 创建快捷方式 需要权限: <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORT ...
- 【特别好】【位运算】maximum-xor-of-two-numbers-in-an-array
https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/ 利用了异或的”自反性“: a ^ b = c,而a ^ b ...
- 流畅的python第十二章继承的优缺点学习记录
子类化内置类型的缺点 多重集成和方法解析顺序 tkinter
- 一个故事讲清NIO
假设某银行只有10个职员.该银行的业务流程分为以下4个步骤: 1) 顾客填申请表(5分钟): 2) 职员审核(1分钟): 3) 职员叫保安去金库取钱(3分钟): 4) 职员打印票据,并将钱和票据返回给 ...
- 将本地jar包添加到maven中
将需要引入的jar包拷贝到maven项目的WEB-INF/lib中 在pom.xml中配置如下: <dependency> <groupId>com.xxxxx.union&l ...
- 解决Android NDK 报jxxx编译找不到
如题 解决: 引入NDK对应的arm或者x86库 如果你用的是GinyMotion模拟器,那就引入x86库 不多说了,见截图 1.选择Propertities->C/C++ General-&g ...
- Django 学习记录
这是我自己理解并自己画的,django 请求示意图,表示了它的组织方式. project manage.py: 主要工具文件 settings.py: 配置文件 urls.py: url 定义及其指向 ...