一、背景

在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类型的话,会报错。接下来我会新开一文说说这个问题。你可以做个例子试试看。

@JeffWong 
非常不错。之前用mvc3,发现里面的JsonResult用的是framework内置的JavaScriptSerializer,对时间处理也有bug

@双调

引用{"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枚举值的更多相关文章

  1. Python中模拟enum枚举类型的5种方法分享

    这篇文章主要介绍了Python中模拟enum枚举类型的5种方法分享,本文直接给出实现代码,需要的朋友可以参考下   以下几种方法来模拟enum:(感觉方法一简单实用) 复制代码代码如下: # way1 ...

  2. 当实体类中entity/DTO/VO等类中,有枚举值,应该怎么输出?

    当实体类中entity/DTO/VO等类中,有枚举值,应该怎么输出? 问题: orderStatus 和 payStatus都是枚举类,并且枚举的个数达地10来个,我们不可能在模板页面(jsp/ftl ...

  3. 获取Enum枚举值描述的几法方法

    原文:获取Enum枚举值描述的几法方法 1.定义枚举时直接用中文 由于VS对中文支持的很不错,所以很多程序员都采用了此方案. 缺点:1.不适合多语言 2.感觉不太完美,毕竟大部分程序员大部分代码都使用 ...

  4. Java中的enum枚举类

    首先说说为什么要写这个enum枚举类吧,是群里有个新手问:怎样把enum类中的值遍历得到,其实自己用的也很少.自己也是确实不知道,于是我去网上搜了不少,总结了些,希望对大家有帮助:首先我说说怎样遍历枚 ...

  5. 在C#中如何读取枚举值的描述属性

    在C#中,有时候我们需要读取枚举值的描述属性,也就是说这个枚举值代表了什么意思.比如本文中枚举值 Chinese ,我们希望知道它代表意思的说明(即“中文”). 有下面的枚举: 1 2 3 4 5 6 ...

  6. Enum 枚举值 (一) 获取描述信息

    封装了方法: public static class EnumOperate { public class BaseDescriptionAttribute : DescriptionAttribut ...

  7. Java中的Enum枚举类型总结

    废话不多说,直接上代码,该例子来源于:http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html public enum Planet { ...

  8. mysql中的 enum (枚举)

    mysql enum是指字段的类型 表示枚举类型 mysql> alter table student add adders enum("sichuang","sh ...

  9. C# 中的 enum(枚举) 类型使用例子

    一.需要根据数字获取中文名称,C# 代码里面出现if 或switch 判断语句,比如下面的类为test1.class //获取计算类型的值 string AggregateType = string. ...

随机推荐

  1. MySQL_关于用嵌套表计算的可以不用 20161205

    计算求和类的指标,其实用不到嵌套表,比如计算各城市产品分类的订单额. 如果要计算不重复的指标 比如一个用户一天下了多个订单 用这样的表计算一天有多少用户下单 这个用户肯定是去重的 下多个订单也应该视为 ...

  2. how spring resolves a request

    quoted from answer at http://stackoverflow.com/questions/14015642/how-does-the-dispatcherservlet-res ...

  3. Jquery Mobile 动态添加元素然后刷新 转

    Jquery Mobile 动态添加元素然后刷新 (2013-05-09 12:39:05) 转载▼ 标签: it 分类: Mobile jquery 表单元素每一个元素都有自己刷新的方法,每当改变它 ...

  4. TCP通讯socket自定义协议的实现

    转发(JAVA):http://blog.csdn.net/u010818425/article/details/53448817 一个简单的自定义通信协议(socket),http://blog.c ...

  5. java内存知识

    java对内存的分类. (网上资料)程序中用来存放数据的内存分为四块,另有一块用于存放代码 1.堆:存放所有new出来的对象(我们知道java并没有全局变量这个概念,有人是把它单独放在properti ...

  6. Selenium2+python自动化9-CSS定位语法

    前言 大部分人在使用selenium定位元素时,用的是xpath定位,因为xpath基本能解决定位的需求.css定位往往被忽略掉了,其实css定位也有它的价值,css定位更快,语法更简洁.这一篇css ...

  7. c++ 递归斐波那契算法及时间复杂度

    #include<iostream> int fib(int n){ ) return n; else ) + fib(n-); } int main(){ ;i<;i++){ st ...

  8. PHP 四种基本排序算法的代码实现

    前提:分别用冒泡排序法,快速排序法,选择排序法,插入排序法将下面数组中的值按照从小到大的顺序进行排序. $arr(1,43,54,62,21,66,32,78,36,76,39); 1. 冒泡排序 思 ...

  9. Vmware虚拟机克隆的网卡问题

    系统环境:red hat 6.4 在虚拟机上使用克隆后,克隆机没有eth0, 出现eth1并且出错No suitable device found: no device found for conne ...

  10. Top Data Scientists to Follow & Best Data Science Tutorials on GitHub

    http://www.analyticsvidhya.com/blog/2015/07/github-special-data-scientists-to-follow-best-tutorials/ ...