最近将一个项目从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开源的消息时,心想这下终于可以一睹世界顶级公司顶尖程序员写的赏心悦目的漂亮代码了!现在却让人有一点点失望。。。

最新文章

  1. 掌握 Linux PC 性能之基准测试
  2. [Nhibernate]对象状态
  3. Java中前台JSP请求Servlet实例(http+Servlet)
  4. 安装Composer 步骤
  5. ASP.NET动态加载用户控件的方法
  6. RMAN备份演练进阶篇
  7. (转载)让ie6也支持max-width,和max-height实现图片等比例缩放
  8. SWT中Display和Shell是个什么东东
  9. VS编辑器主题变换插件-EasyVS
  10. Common.Logging源码解析二
  11. 算法第四版中 while (!StdIn.isEmpty()) 循环无法跳出问题
  12. IP网际协议
  13. 后台自动运行,定期记录定位数据(Hbuilder监听 app由前台切换到后台、切换运行环境的 监听方法)
  14. 【AtCoder】ARC080
  15. python开发-与其他语言的比较
  16. 4A - 排序
  17. 决胜 Poker
  18. 《Windows核心编程》第七章——进程优先级实验
  19. JSON与Bean互转
  20. jQuery中异步请求

热门文章

  1. Cloudstack介绍(一)
  2. Java基础-DButils工具类(QueryRunner)详解
  3. echarts地图扩展___自定义的svg图
  4. mq使用场景、不丢不重、时序性
  5. 已经菜到不行了 PAT 1010. Radix (25)
  6. python 操作excel格式化及outlook正文,发送邮件
  7. Xilinx Altera FPGA中的逻辑资源(Slices VS LE)比较
  8. HDU 4639 hehe 杭电2013多校联赛第四场1008题
  9. c++刷题(21/100)树的打印、矩阵覆盖和括号生成
  10. 配置多个ssh-key