ASP.NET MVC 3升级至MVC 5.1的遭遇:“已添加了具有相同键的项”
最近将一个项目从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`.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`.Begin(AsyncCallback callback, Object state, Int32 timeout)
在 System.Web.Mvc.Async.AsyncResultWrapper.Begin[TResult](AsyncCallback callback, Object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate` 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 = ; 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", );
_innerDictionary.Add("pageIndex", );
引爆了异常:
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中),这不像是出自一个优秀程序员之手。
当时听到ASP.NET MVC开源的消息时,心想这下终于可以一睹世界顶级公司顶尖程序员写的赏心悦目的漂亮代码了!现在却让人有一点点失望。。。
ASP.NET MVC 3升级至MVC 5.1的遭遇:“已添加了具有相同键的项”的更多相关文章
- MVC 5.1的遭遇:“已添加了具有相同键的项”
ASP.NET MVC 3升级至MVC 5.1的遭遇:“已添加了具有相同键的项” 最近将一个项目从ASP.NET MVC 3升级至刚刚发布的ASP.NET MVC 5.1,升级后发现一个ajax请 ...
- MVC :“已添加了具有相同键的项”
最近将一个项目从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已添加了具有相同键的项
异常详细信息: System.ArgumentException: 已添加了具有相同键的项. 场景重现:在地址栏输入 http://localhost:51709/Home/Index?user[0 ...
- MVC 表单提交提示:已添加了具有相同键的项。
MVC:页面提交的时候报如下错误: 解决方案: 这个Model 里面定义了重复的字段so~~~
- Configuring Autofac to work with the ASP.NET Identity Framework in MVC 5
https://developingsoftware.com/configuring-autofac-to-work-with-the-aspnet-identity-framework-in-mvc ...
- ASP.NET CORE 1.0 MVC API 文档用 SWASHBUCKLE SWAGGER实现
from:https://damienbod.com/2015/12/13/asp-net-5-mvc-6-api-documentation-using-swagger/ 代码生成工具: https ...
- ASP.NET 学习小记 -- “迷你”MVC实现(2)
Controller的激活 ASP.NET MVC的URL路由系统通过注册的路由表对HTTO请求进行解析从而得到一个用户封装路由数据的RouteData对象,而这个过程是通过自定义的UrlRoutin ...
- 【.NET特供-第三季】ASP.NET MVC系列:MVC与三层图形对照(颠覆性理论)
在[.NET特供-第三季]系列博客中的第一篇<ASP.NET MVC系列:MVC与三层图形对照>发表之后,引起了领导的注意.同一时候,开发小组内部在交流MVC和三层之间关系的 ...
随机推荐
- 跟上Java8 - 日期和时间实用技巧
原文出处:王爵nice 当你开始使用Java操作日期和时间的时候,会有一些棘手.你也许会通过System.currentTimeMillis() 来返回1970年1月1日到今天的毫秒数.或者使用Dat ...
- codevs 3235 战争
3235 战争 http://codevs.cn/problem/3235/ 时间限制: 2 s 空间限制: 128000 KB 题目描述 Description 2050年,人类与外星人之间 ...
- 如何利用mount命令挂载另一台服务器上的目录
文件服务器(被挂载机):192.168.1.100 操作机(挂载到机):192.168.1.200 也就是说,你在操作机上进行的操作,实际上都到文件服务器上去了: 1. 开启NFS服务: 在文件服务器 ...
- Django 2.0.1 官方文档翻译: 编写你的第一个 Django app,第七部分(Page 12)
编写你的第一个 Django app,第七部分(Page 12)转载请注明链接地址 本节教程承接第六部分(page 11)的教程.我们继续开发 web-poll应用,并专注于自定义django的自动生 ...
- EM算法(Expectation Maximization Algorithm)
EM算法(Expectation Maximization Algorithm) 1. 前言 这是本人写的第一篇博客(2013年4月5日发在cnblogs上,现在迁移过来),是学习李航老师的< ...
- 转【Zabbix性能调优:配置优化】
转载:https://sre.ink/zabbix-turn-conf/ #通过日志可以分析当前服务状态.LogFile=/tmp/zabbix_server.log #日志文件路径.LogFileS ...
- 双11怎么那么强!之二:浅析淘宝网络通信库tbnet的实现
最近开始看Tair的源码实现,Tair的通信使用的是淘宝的开源的网络库tbnet实现.具体来说是依靠tbnet::Transport类型实现,其源代码路径如下:http://code.taobao.o ...
- bzoj 3790 神奇项链(Manacher,DP+BIT | 贪心)
[题意] 你可以产生一个回文串,也可以将两个串合并成一个串,问产生目标串需要的最少合并次数. [思路] 显然我们要先产生目标串中包含的极大回文字符串. Manacher求出每个位置可以向两边延伸的最长 ...
- 《区块链100问》第82集:应用类项目Golem
Golem是第一个基于以太坊区块链打造的计算资源交易平台.通过区块链,Golem能链接全球的算力资源,从而实现计算能力的全球共享.应用所有者和个体用户(算力“请求方”)可以点对点地从其他用户处租用算力 ...
- 简析CSRF
1.简介 CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF ...