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`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中),这不像是出自一个优秀程序员之手。

当时听到ASP.NET MVC开源的消息时,心想这下终于可以一睹世界顶级公司顶尖程序员写的赏心悦目的漂亮代码了!现在却让人有一点点失望。。。

 
 
 
标签: ASP.NET MVC

MVC 5.1的遭遇:“已添加了具有相同键的项”的更多相关文章

  1. ASP.NET MVC 3升级至MVC 5.1的遭遇:“已添加了具有相同键的项”

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

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

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

  3. mvc已添加了具有相同键的项

    异常详细信息: System.ArgumentException: 已添加了具有相同键的项. 场景重现:在地址栏输入  http://localhost:51709/Home/Index?user[0 ...

  4. 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 ...

  5. MVC 表单提交提示:已添加了具有相同键的项。

    MVC:页面提交的时候报如下错误: 解决方案: 这个Model 里面定义了重复的字段so~~~

  6. ArgumentException: 已添加了具有相同键的项。

    此问题出现在asp.net mvc 5 中,前端向后端请求数据,方法的参数是模型,比如 Add(Student m), 结果浏览器显示的状态是500并返回错误提示ArgumentException,如 ...

  7. 多线程环境下非安全Dictionary引起的“已添加了具有相同键的项”问题

    问题: 代码是在多线程环境下,做了简单的Key是否存的判断, 测试代码如下: public class Program { static Dictionary<string, Logger> ...

  8. vs2015 安装之后安装MSSM 2016 导致 vs启动报错 System.ArgumentException 已添加了具有相同键的项,ActivityLog.xml

    如题,先是装了vs2015,开发什么的都没有问题,后来安装了SqlServer2016 MSSM,出大问题了,vs2015打开就报错,具体错误如上,还想还有个ActivityLog.xml 这个文件的 ...

  9. VS窗体选择BackGroupImage属性报错:已添加具有相同键的项

    高墙我今天第一次遇见这个问题.既然说是"已添加具有相同键的项."那我自然地认为会不会是文件夹哪里命名了两个相同的文件名.然后在这个Exception上越走越远. 好了不说废话.出现 ...

随机推荐

  1. android tv 全屏幕垂直画

    它们的定义view 采纳canvas双缓冲方式,它可以减少呈现时间.提高性能. StaggeredView.java 源码例如以下: package com.xxx.demo; import andr ...

  2. 中国人被“清朝GDP世界第一”忽悠了!

    中国人被"清朝GDP世界第一"忽悠了!"鸦片战争前的清朝GDP世界第一",这一说法在中国流传非常广.追根溯源,最早提出这一观点的似乎是英国学者麦迪森,他的一项猜 ...

  3. python基础课程_学习笔记21:文件和材料

    文件和材料 打开文件 open功能是用来打开文件,语法例如,下面的: open([name[,mode[,buffering]]) open函数使用一个文件名称作为唯一的强制參数,然后返回一个文件对象 ...

  4. Matrix (二维树状数组)

    Description Given an N*N matrix A, whose elements are either 0 or 1. A[i, j] means the number in the ...

  5. RabbitHub开源

    RabbitHub开源情况及计划   之前写过一篇”.NET 平台下的插件化开发内核(Rabbit Kernel)”,已经过去三个月了,期间RabbitHub并不是没有了发展更不是放弃了发展,在Rab ...

  6. POJ 3013 Big Christmas Tree(最短Dijkstra+优先级队列优化,SPFA)

    POJ 3013 Big Christmas Tree(最短路Dijkstra+优先队列优化,SPFA) ACM 题目地址:POJ 3013 题意:  圣诞树是由n个节点和e个边构成的,点编号1-n. ...

  7. ABP分层架构

    ABP分层架构 基于DDD的现代ASP.NET开发框架--ABP系列之3.ABP分层架构 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ABP ...

  8. javascript利用map,every,filter,some,reduce,sort对数组进行最优化处理

    案例: var scoresTable=[ {id:11,name:"小张",score:80}, {id:22,name:"小王",score:95}, {i ...

  9. winform 实现选择的城市名单

    首先在地图上 #region 选择城市 /// <summary> /// 点击字母事件 /// </summary> /// <param name="sen ...

  10. hdu 1002 Java 大数 加法

    http://acm.hdu.edu.cn/showproblem.php?pid=1002 PE   由于最后一个CASE不须要输出空行 import java.math.BigInteger; i ...