MVC3不能正确识别JSON中的Enum枚举值
一、背景
在MVC3项目里,如果Action的参数中有Enum枚举作为对象属性的话,使用POST方法提交过来的JSON数据中的枚举值却无法正确被识别对应的枚举值。
二、Demo演示
为了说明问题,我使用MVC3项目创建Controller,并且创建如下代码演示:
//交通方式枚举
public enum TrafficEnum
{
Bus = ,
Boat = ,
Bike = ,
}
public class Person
{
public int ID { get; set; }
public TrafficEnum Traffic { get; set; }
} public class DemoController : Controller
{
public ActionResult Index(Person p)
{
return View();
}
}
网站生成成功之后,就可以使用Fiddler来发送HTTP POST请求了,注意需要的是,要在Request Headers加上请求头content-type:application/json,这样才能通知服务器端Request Body里的内容为JSON格式。

点击右上角的Execute执行HTTP请求,在程序断点情况下,查看参数p,属性ID已经正确的被识别到了值为9999,而枚举值属性Traffic却被错认为枚举中的首个值Bus,这俨然是错误的,纵使你将Traffic修改成Bike,也就是值等于2,结果也是一样。

三、解决方法
方法一:
升级MVC4,亲测在MVC4项目下,这个问题已经被修复了;
方法二:
假若因为各种原因,项目不想或者不能升级为MVC4,可以在MVC3项目上做些改动,亦可修复这个问题,
1、在项目中,新建一个类,加入以下代码,需要引用一下 using System.ComponentModel; using System.Web.Mvc; 命名空间;
/// <summary>
/// 处理在MVC3下,提交的JSON枚举值在Controller不能识别的问题
/// </summary>
public class EnumConverterModelBinder : DefaultModelBinder
{
protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
{
var propertyType = propertyDescriptor.PropertyType;
if (propertyType.IsEnum)
{
var providerValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (null != providerValue)
{
var value = providerValue.RawValue;
if (null != value)
{
var valueType = value.GetType();
if (!valueType.IsEnum)
{
return Enum.ToObject(propertyType, value);
}
}
}
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
2、在Global.asax的Application_Start方法中,进行EnumConverterModelBinder类的实例化操作:
protected void Application_Start()
{
//处理在MVC3下,提交的JSON枚举值在Controller不能识别的问题
ModelBinders.Binders.DefaultBinder = new EnumConverterModelBinder();
}
进行配置改造之后,我再次生成网站,重新发送HTTP请求看,MVC Action中的参数里的枚举就能被正确的识别到了。

四、研究
我觉得这应该是mvc3里面一个小小的缺陷吧,随着mvc的升级,这已经在新版本里被完善修复了,可还用着mvc3的人如果在项目中遇到这个问题,可以研究一下。
遇到一个问题,去百度谷歌找解决方案是可以,但是复制粘贴完代码之后,最好问下自己,为什么这样可以解决问题。
从现象和解决代码中猜想,应该是在MVC生命周期中的Model Binders 这一环节出了问题。

因为MVC已经开源了,所以我尝试着调试源码,首先下载MVC3的源码,其他项目可以移除,只保留红色框中的项目即可,然后新建一个MVC3测试项目,并且将此测试项目的system.web.mvc引用移除,转而引用本解决方案中的system.web.mvc 项目,这样子,我们才可以对MVC源码进行调试操作。

搜回来的代码中可知,我们自定义的类继承DefaultModelBinder父类,并且重写了GetPropertyValue方法,那我们就从这点开始,在MVC3源码中的System.Web.MVC项目中找到该类,在此方法上插入断点。

F5调试程序,发送一个POST请求。
其实BindProperty方法是会被多次执行的,BindProperties方法会对请求的实体类的属性进行遍历,每一个属性都要经过BindProperty方法的处理;

现在已经截获到第一个属性ID了。

紧接着,程序进入propertyBinder.BindModel 方法。
只贴部分关键代码了,通过bindingContext的ValueProvider 获得属性的相关信息,如果不等于null的话,转到执行BindSimpleModel 方法。

在BindSimpleModel方法里,首先通过Type.IsInstanceOfType方法判断确定指定的对象是否是当前 Type 的实例,如果是,则直接返回rawValue,这里的属性类型是Int32类型,返回True符合条件,所以直接把rawValue给返回去了。

第一个Int32类型属性的部分关键代码执行到这里就已经确认到值了,接下来,我们看出了问题的Enum枚举类型属性。

循环来到了第二个属性了,这时我留意到有个Model属性,对比Int32类型执行的时候,这个属性当时为0,而此时则为Bus,可见这是一个默认值,指定枚举中值为0的那个类型(即使你不为枚举显式指定值),同样的,经过BindModel方法来到了BindSimpleModel方法。

此时,对比Int32类型的属性ID,这次ModelType.IsInstanceOfType(valueProvideResult.RawValue)为False,并且接下来不是string类型就执行以下的判断,也不是数组类型,所以,来到了最后一个,根据绿色的注释可以看出,这应该是一个判断是否collection集合类型的方法,Enum都不是,所以,返回了Null。
这时,Type collectionType变量为Null,执行最后一个case 3

在ConvertProviderResult方法里,也进行了一系列的类型判断转换,目的就是将JSON中的数字类型转换成枚举值,但是执行过程中抛出异常了,原因是
“No type converter can convert between these types ” 也就是说,在MVC3的机制中,并没有相应的type converter来处理数值与枚举的对应。

经过以上这些处理方法,都没完成把对应的值确认下来,怎么给原来的BindProperty 老大方法交差呢,所以,小的只好将Value=Null 和 modelState.Errors 模型错误状态信息如实带回去了,让老大决定怎么做,老大后面处理这里有点绕,但是我看源码估计也是拿默认值来充当Value了,所以就造成了JSON传过来的值与对应枚举的值不对应的情况,无论传什么值,结果都是第一个枚举的值。

五、总结
这篇文章只是我在工作上遇到的一个小问题,然后有点小兴趣就从源码的角度上来研究和分析,缺乏理论的依据,因为之前没有很深入的去研究MVC的底层运行机制与生命周期,所以这方面还需要得加强学习一下,如果你也有兴趣,可以下载我修改好的源码来分析一下,甚至可以下载MVC4的源码来进行对比。
六、后续
感谢各位园友的支持,本文上了昨天博客园的首页推荐,也有很多园友给出一些非常有用的建议,特此贴出,以飨园友!
@eflay
引用干嘛不升级4。。。 话说MVC里的json转换都用json.net替换掉了。
dotnetgeek回复:文章中,已经提到可以升级MVC4了,但是我今天又发现了另外一个问题,在MVC3中,Dictionary也有问题,而且,升级到MVC4之后,如果Key为int类型的话,会报错。接下来我会新开一文说说这个问题。你可以做个例子试试看。
@双调
引用{"ID":9999,"Traffic":"1"} 就好了。
理论上number也要带上“,只是很多解析器做了处理。
不要把简单的东西复杂化。
dotnetgeek回复: 亲测确实可以,不过,你不觉得如果由前端传过来的数据没有带引号,就能令MVC内部报异常从而导致数据不正确,是一件很不妥的事情吗?另外,我昨天也发现Dictionary也会有这样的问题,并且前端传过来的数据是带上了双引号的,MVC绑定也不能正确的识别,并且在MVC4里面,如果Dictionary的Key如果为int类型的话,JSON(obj)序列化输出的时候,还会报错。
ps:关于Dictionary的问题
关于Dictionary其实也有相关的问题,并且在MVC4中还有其他问题,这我应该会新开一文来解一下,这里做个提醒的是:
用Dictionary类型序列化之后的JSON格式字符串,提交给action,action是不认得的,经过在stackoverflow找到了解决方法,原来提交的时候,格式需要特殊处理一下,按照KeyValue键值对来的,而非序列化后的格式,各位注意了。
正确被识别Dictionary的JSON: { "ID":"9999","Traffic":"1","Dic":[{"Key":1,"Value":"xyz"},{"Key":2,"Value":42}] }
不能被识别Dictionary的JSON: { "ID":"9999","Traffic":"1","Dic":{"1":"xyz","2":"42"} } 但此格式又是被 JSON(obj) 序列化后的格式。
MVC3不能正确识别JSON中的Enum枚举值的更多相关文章
- Python中模拟enum枚举类型的5种方法分享
这篇文章主要介绍了Python中模拟enum枚举类型的5种方法分享,本文直接给出实现代码,需要的朋友可以参考下 以下几种方法来模拟enum:(感觉方法一简单实用) 复制代码代码如下: # way1 ...
- 当实体类中entity/DTO/VO等类中,有枚举值,应该怎么输出?
当实体类中entity/DTO/VO等类中,有枚举值,应该怎么输出? 问题: orderStatus 和 payStatus都是枚举类,并且枚举的个数达地10来个,我们不可能在模板页面(jsp/ftl ...
- 获取Enum枚举值描述的几法方法
原文:获取Enum枚举值描述的几法方法 1.定义枚举时直接用中文 由于VS对中文支持的很不错,所以很多程序员都采用了此方案. 缺点:1.不适合多语言 2.感觉不太完美,毕竟大部分程序员大部分代码都使用 ...
- Java中的enum枚举类
首先说说为什么要写这个enum枚举类吧,是群里有个新手问:怎样把enum类中的值遍历得到,其实自己用的也很少.自己也是确实不知道,于是我去网上搜了不少,总结了些,希望对大家有帮助:首先我说说怎样遍历枚 ...
- 在C#中如何读取枚举值的描述属性
在C#中,有时候我们需要读取枚举值的描述属性,也就是说这个枚举值代表了什么意思.比如本文中枚举值 Chinese ,我们希望知道它代表意思的说明(即“中文”). 有下面的枚举: 1 2 3 4 5 6 ...
- Enum 枚举值 (一) 获取描述信息
封装了方法: public static class EnumOperate { public class BaseDescriptionAttribute : DescriptionAttribut ...
- Java中的Enum枚举类型总结
废话不多说,直接上代码,该例子来源于:http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html public enum Planet { ...
- mysql中的 enum (枚举)
mysql enum是指字段的类型 表示枚举类型 mysql> alter table student add adders enum("sichuang","sh ...
- C# 中的 enum(枚举) 类型使用例子
一.需要根据数字获取中文名称,C# 代码里面出现if 或switch 判断语句,比如下面的类为test1.class //获取计算类型的值 string AggregateType = string. ...
随机推荐
- 一个最简单的登录页面测试case
具体需求: 有一个登陆页面, (假如上面有2个textbox, 一个提交按钮. 请针对这个页面设计30个以上的testcase.) 此题的考察目的:面试者是否熟悉各种测试方法,是否有丰富的Web测试经 ...
- 未在本地计算机上注册“OraOLEDB.Oracle.1”提供程序解决方案
未在本地计算机上注册“OraOLEDB.Oracle.1”提供程序解决方案 一.现象: C#程序中需要以Provider=OraOLEDB.Oracle.1方式访问ORACLE数据库.但程序执行时报 ...
- 编程范式 episode 6 实现stack 栈功能_ to do
//既然在这里开始,那就在这里结束. 实现stack 功能 ____coding _using subfunction to focus on the main aim of current func ...
- VS2008中调试dll
1.运行dll实例时,会直接弹出一个小框: 选择可拉起这个dll的exe运行就可以调试了 2.以后每次都会直接运行了,要重新选择程序,弹出上面的框,需要在project-->debugging- ...
- arm64 boot
http://www.wowotech.net/linux_kenrel/arm64_initialize_1.html
- [转]LIBSVM-3.18在python环境下的使用
http://blog.csdn.net/lj695242104/article/details/39500039 1)安装Python,直接去官方网站 2)运行“cmd”,直接输入python,查看 ...
- java文件乱码
要让一个 Java 源文件打开时编码格式为 UTF-8,需要做2件事情:1)设置Java 源文件的默认编码格式为UTF-8:2)设置workspace的编码格式为UTF-8. 相应设置如下: 设置 J ...
- System.IO中的File、FileInfo、Directory与DirectoryInfo类(实例讲解)
一.建立的文件夹(对这些文件进行以上四个类的操作): 父目录: 父目录的子目录以及父目录下的文件: 子目录下的文件: 二.效果图 三.代码实现 using System; using System.I ...
- Selenium2+python自动化23-富文本(自动发帖)
前言 富文本编辑框是做web自动化最常见的场景,有很多小伙伴遇到了不知道无从下手,本篇以博客园的编辑器为例,解决如何定位富文本,输入文本内容 一.加载配置 1.打开博客园写随笔,首先需要登录,这里为了 ...
- Python全栈--6.1-match-search-findall-group(s)的区别以及计算器实例
match.search.findall.group(s) 区别 import re # match findall经常用 # re.match() #从开头匹配,没有匹配到对象就返回NONE # r ...