Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.Net MVC3 - why does the default support for JSON model binding fail to decode to enum types?

I am having an issue with ASP.Net MVC3 (RC2). I'm finding that the new JSON model binding functionality, which is implicit in MVC3, does not want to deserialize to a property that has an enum type.

Here's a sample class and enum type:

public enum MyEnum { Nothing = 0, SomeValue = 5 }
public class MyClass
{
    public MyEnum Value { get; set; }
    public string OtherValue { get; set; }
}

Consider the following code, which successfully passes the unit test:

[TestMethod]
public void Test()
{
    var jss = new JavaScriptSerializer();
    var obj1 = new MyClass { Value = MyEnum.SomeValue };
    var json = jss.Serialize(obj1);
    var obj2 = jss.Deserialize<MyClass>(json);
    Assert.AreEqual(obj1.Value, obj2.Value);
}

If I serialize obj1 above, but then post that data to an MVC3 controller (example below) with a single parameter of type MyClass, any other properties of the object deserialize properly, but any property that is an enum type deserializes to the default (zero) value.

[HttpPost]
public ActionResult TestAction(MyClass data)
{
    return Content(data.Value.ToString()); // displays "Nothing"
}

I've downloaded the MVC source code from codeplex but I'm stumped as to where the actual code performing the deserialization occurs, which means I can't work out what the folks at Microsoft have used to perform the deserialization and thus determine if I'm doing something wrong or if there is a workaround.

Any suggestions would be appreciated.

like image 255
Nathan Ridley Avatar asked Jan 03 '11 05:01

Nathan Ridley


1 Answers

I've found the answer. I hope this is fixed in MVC3 RTM, but essentially what happens is the object deserializes correctly internally via JsonValueProviderFactory, which uses JavaScriptSerializer to do the work. It uses DeserializeObject() so that it can pass the values back to the default model binder. The problem is that the default model binder won't convert/assign an int value when the property type is an enum.

There is a discussion of this at the ASP.Net forums here:
http://forums.asp.net/p/1622895/4180989.aspx

The solution discussed there is to override the default model binder like so:

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);
    }
}

Then in Application_Start, add the following line:

ModelBinders.Binders.DefaultBinder = new EnumConverterModelBinder();
like image 193
Nathan Ridley Avatar answered Nov 13 '22 14:11

Nathan Ridley