新聞中心
一、背景
在MVC3項(xiàng)目里,如果Action的參數(shù)中有Enum枚舉作為對(duì)象屬性的話,使用POST方法提交過來的JSON數(shù)據(jù)中的枚舉值卻無法正確被識(shí)別對(duì)應(yīng)的枚舉值。

創(chuàng)新互聯(lián)專注于企業(yè)營銷型網(wǎng)站建設(shè)、網(wǎng)站重做改版、泰寧網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5高端網(wǎng)站建設(shè)、商城建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為泰寧等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
二、Demo演示
為了說明問題,我使用MVC3項(xiàng)目創(chuàng)建Controller,并且創(chuàng)建如下代碼演示:
- //交通方式枚舉
- public enum TrafficEnum
- {
- Bus = 0,
- Boat = 1,
- Bike = 2,
- }
- public class Person
- {
- public int ID { get; set; }
- public TrafficEnum Traffic { get; set; }
- }
- public class DemoController : Controller
- {
- public ActionResult Index(Person p)
- {
- return View();
- }
- }
網(wǎng)站生成成功之后,就可以使用Fiddler來發(fā)送HTTP POST請求了,注意需要的是,要在Request Headers加上請求頭content-type:application/json,這樣才能通知服務(wù)器端Request Body里的內(nèi)容為JSON格式。
點(diǎn)擊右上角的Execute執(zhí)行HTTP請求,在程序斷點(diǎn)情況下,查看參數(shù)p,屬性ID已經(jīng)正確的被識(shí)別到了值為9999,而枚舉值屬性Traffic卻被錯(cuò)認(rèn)為枚舉中的首個(gè)值Bus,這儼然是錯(cuò)誤的,縱使你將Traffic修改成Bike,也就是值等于2,結(jié)果也是一樣。
三、解決方法
方法一:
升級(jí)MVC4,親測在MVC4項(xiàng)目下,這個(gè)問題已經(jīng)被修復(fù)了;
方法二:
假若因?yàn)楦鞣N原因,項(xiàng)目不想或者不能升級(jí)為MVC4,可以在MVC3項(xiàng)目上做些改動(dòng),亦可修復(fù)這個(gè)問題,
1、在項(xiàng)目中,新建一個(gè)類,加入以下代碼,需要引用一下 using System.ComponentModel; using System.Web.Mvc; 命名空間;
- ///
- /// 處理在MVC3下,提交的JSON枚舉值在Controller不能識(shí)別的問題
- ///
- 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方法中,進(jìn)行EnumConverterModelBinder類的實(shí)例化操作:
- protected void Application_Start()
- {
- //處理在MVC3下,提交的JSON枚舉值在Controller不能識(shí)別的問題
- ModelBinders.Binders.DefaultBinder = new EnumConverterModelBinder();
- }
進(jìn)行配置改造之后,我再次生成網(wǎng)站,重新發(fā)送HTTP請求看,MVC Action中的參數(shù)里的枚舉就能被正確的識(shí)別到了。
#p#
四、研究
我覺得這應(yīng)該是mvc3里面一個(gè)小小的缺陷吧,隨著mvc的升級(jí),這已經(jīng)在新版本里被完善修復(fù)了,可還用著mvc3的人如果在項(xiàng)目中遇到這個(gè)問題,可以研究一下。
遇到一個(gè)問題,去百度谷歌找解決方案是可以,但是復(fù)制粘貼完代碼之后,最好問下自己,為什么這樣可以解決問題。
從現(xiàn)象和解決代碼中猜想,應(yīng)該是在MVC生命周期中的Model Binders 這一環(huán)節(jié)出了問題。
因?yàn)镸VC已經(jīng)開源了,所以我嘗試著調(diào)試源碼,首先下載MVC3的源碼,其他項(xiàng)目可以移除,只保留紅色框中的項(xiàng)目即可,然后新建一個(gè)MVC3測試項(xiàng)目,并且將此測試項(xiàng)目的system.web.mvc引用移除,轉(zhuǎn)而引用本解決方案中的system.web.mvc 項(xiàng)目,這樣子,我們才可以對(duì)MVC源碼進(jìn)行調(diào)試操作。
搜回來的代碼中可知,我們自定義的類繼承DefaultModelBinder父類,并且重寫了GetPropertyValue方法,那我們就從這點(diǎn)開始,在MVC3源碼中的System.Web.MVC項(xiàng)目中找到該類,在此方法上插入斷點(diǎn)。
F5調(diào)試程序,發(fā)送一個(gè)POST請求。
其實(shí)BindProperty方法是會(huì)被多次執(zhí)行的,BindProperties方法會(huì)對(duì)請求的實(shí)體類的屬性進(jìn)行遍歷,每一個(gè)屬性都要經(jīng)過BindProperty方法的處理;
現(xiàn)在已經(jīng)截獲到第一個(gè)屬性ID了。
緊接著,程序進(jìn)入propertyBinder.BindModel 方法。
只貼部分關(guān)鍵代碼了,通過bindingContext的ValueProvider 獲得屬性的相關(guān)信息,如果不等于null的話,轉(zhuǎn)到執(zhí)行BindSimpleModel 方法。
#p#
在BindSimpleModel方法里,首先通過Type.IsInstanceOfType方法判斷確定指定的對(duì)象是否是當(dāng)前 Type 的實(shí)例,如果是,則直接返回rawValue,這里的屬性類型是Int32類型,返回True符合條件,所以直接把rawValue給返回去了。
第一個(gè)Int32類型屬性的部分關(guān)鍵代碼執(zhí)行到這里就已經(jīng)確認(rèn)到值了,接下來,我們看出了問題的Enum枚舉類型屬性。
循環(huán)來到了第二個(gè)屬性了,這時(shí)我留意到有個(gè)Model屬性,對(duì)比Int32類型執(zhí)行的時(shí)候,這個(gè)屬性當(dāng)時(shí)為0,而此時(shí)則為Bus,可見這是一個(gè)默認(rèn)值,指定枚舉中值為0的那個(gè)類型(即使你不為枚舉顯式指定值),同樣的,經(jīng)過BindModel方法來到了BindSimpleModel方法。
此時(shí),對(duì)比Int32類型的屬性ID,這次ModelType.IsInstanceOfType(valueProvideResult.RawValue)為False,并且接下來不是string類型就執(zhí)行以下的判斷,也不是數(shù)組類型,所以,來到了最后一個(gè),根據(jù)綠色的注釋可以看出,這應(yīng)該是一個(gè)判斷是否collection集合類型的方法,Enum都不是,所以,返回了Null。
這時(shí),Type collectionType變量為Null,執(zhí)行最后一個(gè)case 3
在ConvertProviderResult方法里,也進(jìn)行了一系列的類型判斷轉(zhuǎn)換,目的就是將JSON中的數(shù)字類型轉(zhuǎn)換成枚舉值,但是執(zhí)行過程中拋出異常了,原因是
“No type converter can convert between these types ” 也就是說,在MVC3的機(jī)制中,并沒有相應(yīng)的type converter來處理數(shù)值與枚舉的對(duì)應(yīng)。
經(jīng)過以上這些處理方法,都沒完成把對(duì)應(yīng)的值確認(rèn)下來,怎么給原來的BindProperty 老大方法交差呢,所以,小的只好將Value=Null 和 modelState.Errors 模型錯(cuò)誤狀態(tài)信息如實(shí)帶回去了,讓老大決定怎么做,老大后面處理這里有點(diǎn)繞,但是我看源碼估計(jì)也是拿默認(rèn)值來充當(dāng)Value了,所以就造成了JSON傳過來的值與對(duì)應(yīng)枚舉的值不對(duì)應(yīng)的情況,無論傳什么值,結(jié)果都是第一個(gè)枚舉的值。
五、總結(jié)
這篇文章只是我在工作上遇到的一個(gè)小問題,然后有點(diǎn)小興趣就從源碼的角度上來研究和分析,缺乏理論的依據(jù),因?yàn)橹皼]有很深入的去研究MVC的底層運(yùn)行機(jī)制與生命周期,所以這方面還需要得加強(qiáng)學(xué)習(xí)一下,如果你也有興趣,可以下載我修改好的源碼來分析一下,甚至可以下載MVC4的源碼來進(jìn)行對(duì)比。
本文標(biāo)題:MVC3不能正確識(shí)別JSON中的Enum枚舉值
標(biāo)題網(wǎng)址:http://fisionsoft.com.cn/article/copcdpc.html


咨詢
建站咨詢
