一、背景

在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. XidianOJ 1096 数的拆分

    题目描述 输入自然数n,然后将其拆分成由若干数相加的形式,参与加法运算的数可以重复. 输入 多组数据.每组只有一个整数n,表示待拆分的自然数n. n<=80 输出 每组一个数,即所有方案数. - ...

  2. gulp-htmlmin压缩html

    通过一条命令用Npm安装gulp-htmlmin: npm install gulp-htmlmin --save-dev 安装完毕后,打开gulpfile.js文件,我们里面编写一个task用来专门 ...

  3. asp.net GDI+ 使用PathGradienBrush类实现彩色渐变

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...

  4. jquery 找不到live方法解决

    http://stackoverflow.com/questions/15573645/typeerror-live-is-not-a-function

  5. lucene源码地址

    http://archive.apache.org/dist/lucene/java/

  6. Cannot assign to 'self' outside of a method in the init family

    今天在重写父类的init方法时报错如下: error:Cannot assign to 'self' outside of a method in the init family 这种问题以前从来没有 ...

  7. Linux网络编程-SIGPIPE信号导致的程序退出问题

    当客户端close关闭连接时,若server端接着发送数据,根据TCP协议的规定,server端会收到RST响应,当server端再次往客户端发送数据时,系统会发出一个SIGPIPE信号给server ...

  8. poj 3368 Frequent values(RMQ)

    /************************************************************ 题目: Frequent values(poj 3368) 链接: http ...

  9. Unity(三)依赖注入

    Unity具体实现依赖注入包含.属性注入.方法注入. 构造函数注入 public void ConStructorCodeTest1() { //默认注册(无命名),如果后面还有默认注册会覆盖前面的 ...

  10. WPF中获得控件相对于控件的相对位置

    GeneralTransform generalTransform = lstitem.TransformToAncestor(this.BackStack); Point point = gener ...