I have a situation where I am sending JSON to an action and using the JsonValueProviderFactory to bind the JSON to my contract. For some reason, enum values are not being bound when the model is deserialized.
The contract (action parameter) is a generic that looks something like the following...
public class Request<ContractType>
{
public string UserName{get;set;}
public string Password {get;set;}
public ContractType Data {get;set;}
}
public class LinkContract
{
public MyEnum MyEnumProperty {get;set;}
}
... and so the action is defined as ...
[HttpPost]
public JsonResult CreateLink(Request<LinkContract> request)
{
...
}
For the most part this is working great, except that any enum properties are not populated correctly after the default model binder runs. I created a custom model binder and checked the DictionaryValueProvider in the bindingContext and it looks like it contains
the correct property name and values for enums, but after the binder runs, the enums are all just set to 0 in the contract.
Any thoughts as to what I did that's getting in the way?
Chuck Kinnan
------------
If you don't like my comments, that's cool.
Use my code freely, but at your own risk.
And stop telling me to mark answers you greedy so-and-sos!
Maybe your problem is the type herarchy in the Action side, that probabley doesn't match the herarchy of the View side. Specifically you have the following prefixes:
Username
Password
Data.MyEnumProperty
This means your enum comes after a first Data prefix. Maybe that if you use a Request<MyEnum> instead of a Request<LinkContract> you delete the Data prefix of your MyEnumProperty and the two herarchy now match.
Though it looks like I oversimplified my example a little bit. Sorry about that. Never know when too much information is just confusing the issue.
Anyway, I don't think your suggestion is going to fix my problem. My LinkContract class has properties in it other than just the enumeration value. Those other properties are successfully populated when I receive the Link contract in the Action method.
A more complete example would be...
[DataContract]
public class LinkContract
{
[DataMember]
public MyEnum MyEnumProperty {get;set;}
[DataMember] public string Name {get;set;}
[DataMember]
public string Location {get;set;}
}
A more complete view of the process is that while in some cases I will be using JQuery to connect to the web services, other times we will be connecting from compiled code. I haven't tested the former for this service yet, the latter case is where the failure
is happening. I am using the same contract dll file on both sides of the application. On the client side I have tried using both DataContractJsonSerializer and JavaScriptSerializer to serialize the contract with the same results. All of the values on the
LinkContract are populated except for any enum values.
I have also tried changing the value in the DictionaryValueProvider from the int value of the enum to the string value of the enum without affect. This makes me think that enums aren't handled correctly in this crazy setup I've got here. But I haven't
confirmed that yet.
I have seen some talk on the web suggesting that I could use the JavaScriptSerializer to modify how enums are handled, but unless I want to write my own model binder for MVC, I'm not sure that this helps me, or is even necessary.
Francesco, your answer gave me some ideas though. I'm going to simplify the contract and see if I can get the enum value through at all. I'm pretty sure I've done that before. Here's hoping.
Chuck Kinnan
------------
If you don't like my comments, that's cool.
Use my code freely, but at your own risk.
And stop telling me to mark answers you greedy so-and-sos!
What I've just found out is that if I push the correct enum value into the DictionaryValueProvider, the LinkContract is populated correctly by the DefaultModelBinder.
If I had to take a guess, I'd say that the JsonValueProviderFactory is not quite in sync with DefaultModelBinder yet. That is, the value provider is supplying integers for enums instead of enum types.
Guess I'll try extending one or the other of these for now.
Thanks for reading.
Chuck Kinnan
------------
If you don't like my comments, that's cool.
Use my code freely, but at your own risk.
And stop telling me to mark answers you greedy so-and-sos!
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);
}
}
Basically, if the model's property is an enum and the value in the value provider is not, convert it. Throw some extra error handling around this and you've got our code. There's probably a better solution involving getting the correct value in the value
provider in the first place, but Reflector doesn't seem to want to run for me so I can't dig into it and this works well enough for now.
Chuck Kinnan
------------
If you don't like my comments, that's cool.
Use my code freely, but at your own risk.
And stop telling me to mark answers you greedy so-and-sos!
I ran into this problem when I found a solution to sending JSON to MVC Actions in
this post. The post describes the simple steps to hook it up and how to pass JSON to an action. The JsonValueProviderFactory is included in the
MVC Futures library.
The problem is that the JsonValueProvider does not create enum values in the value dictionary (it creates ints) and the default model binder doesn't seem to know what to do when ints are provided for enum values. Probably for performance reasons.
The EnumConverterModelBinder code I posted is a custom class I created that checks if the property being bound to is an Enum and the data in the value dictionary is not. In this case only, it converts the dictionary value and returns it to be populated
in the model. If those conditions are not met, the default implementation is used. I tried to do this in a way that minimizes the performance impact on all other property types.
You can set the default model binder in Global.asax. This is from memory at the moment so it may not be 100% correct, but in the Application_Start method, add the following line.
Models.DefaultBinder = new EnumConverterModelBinder()
Hope that helps
Chuck Kinnan
------------
If you don't like my comments, that's cool.
Use my code freely, but at your own risk.
And stop telling me to mark answers you greedy so-and-sos!
onevextchuck
Member
2 Points
6 Posts
JsonValueProviderFactory
Nov 12, 2010 06:21 PM|LINK
I have a situation where I am sending JSON to an action and using the JsonValueProviderFactory to bind the JSON to my contract. For some reason, enum values are not being bound when the model is deserialized.
The contract (action parameter) is a generic that looks something like the following...
public class Request<ContractType>
{
public string UserName{get;set;}
public string Password {get;set;}
public ContractType Data {get;set;}
}
public class LinkContract
{
public MyEnum MyEnumProperty {get;set;}
}
... and so the action is defined as ...
[HttpPost]
public JsonResult CreateLink(Request<LinkContract> request)
{
...
}
For the most part this is working great, except that any enum properties are not populated correctly after the default model binder runs. I created a custom model binder and checked the DictionaryValueProvider in the bindingContext and it looks like it contains the correct property name and values for enums, but after the binder runs, the enums are all just set to 0 in the contract.
Any thoughts as to what I did that's getting in the way?
------------
If you don't like my comments, that's cool.
Use my code freely, but at your own risk.
And stop telling me to mark answers you greedy so-and-sos!
Blog: Cozened Cognizance
francesco ab...
All-Star
20888 Points
3277 Posts
Re: JsonValueProviderFactory
Nov 12, 2010 09:29 PM|LINK
Maybe your problem is the type herarchy in the Action side, that probabley doesn't match the herarchy of the View side. Specifically you have the following prefixes:
Username
Password
Data.MyEnumProperty
This means your enum comes after a first Data prefix. Maybe that if you use a Request<MyEnum> instead of a Request<LinkContract> you delete the Data prefix of your MyEnumProperty and the two herarchy now match.
Mvc Controls Toolkit | Data Moving Plug-in Videos
onevextchuck
Member
2 Points
6 Posts
Re: JsonValueProviderFactory
Nov 15, 2010 03:33 PM|LINK
Thanks for the thoughts on the code.
Though it looks like I oversimplified my example a little bit. Sorry about that. Never know when too much information is just confusing the issue.
Anyway, I don't think your suggestion is going to fix my problem. My LinkContract class has properties in it other than just the enumeration value. Those other properties are successfully populated when I receive the Link contract in the Action method. A more complete example would be...
[DataContract]
public class LinkContract
{
[DataMember]
public MyEnum MyEnumProperty {get;set;}
[DataMember]
public string Name {get;set;}
[DataMember]
public string Location {get;set;}
}
A more complete view of the process is that while in some cases I will be using JQuery to connect to the web services, other times we will be connecting from compiled code. I haven't tested the former for this service yet, the latter case is where the failure is happening. I am using the same contract dll file on both sides of the application. On the client side I have tried using both DataContractJsonSerializer and JavaScriptSerializer to serialize the contract with the same results. All of the values on the LinkContract are populated except for any enum values.
I have also tried changing the value in the DictionaryValueProvider from the int value of the enum to the string value of the enum without affect. This makes me think that enums aren't handled correctly in this crazy setup I've got here. But I haven't confirmed that yet.
I have seen some talk on the web suggesting that I could use the JavaScriptSerializer to modify how enums are handled, but unless I want to write my own model binder for MVC, I'm not sure that this helps me, or is even necessary.
Francesco, your answer gave me some ideas though. I'm going to simplify the contract and see if I can get the enum value through at all. I'm pretty sure I've done that before. Here's hoping.
------------
If you don't like my comments, that's cool.
Use my code freely, but at your own risk.
And stop telling me to mark answers you greedy so-and-sos!
Blog: Cozened Cognizance
onevextchuck
Member
2 Points
6 Posts
Re: JsonValueProviderFactory
Nov 15, 2010 03:49 PM|LINK
What I've just found out is that if I push the correct enum value into the DictionaryValueProvider, the LinkContract is populated correctly by the DefaultModelBinder.
If I had to take a guess, I'd say that the JsonValueProviderFactory is not quite in sync with DefaultModelBinder yet. That is, the value provider is supplying integers for enums instead of enum types.
Guess I'll try extending one or the other of these for now.
Thanks for reading.
------------
If you don't like my comments, that's cool.
Use my code freely, but at your own risk.
And stop telling me to mark answers you greedy so-and-sos!
Blog: Cozened Cognizance
onevextchuck
Member
2 Points
6 Posts
Re: JsonValueProviderFactory
Nov 15, 2010 05:58 PM|LINK
And to sum this all up, this little change to DefaultModelBinder seems to get everything working for me.
<div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"></div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"></div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> public class EnumConverterModelBinder : DefaultModelBinder</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> {</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> {</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> var propertyType = propertyDescriptor.PropertyType;</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> if (propertyType.IsEnum)</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> {</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> var providerValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> if (null != providerValue)</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> {</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> var value = providerValue.RawValue;</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> if (null != value)</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> {</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> var valueType = value.GetType();</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> if (!valueType.IsEnum)</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> {</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> return Enum.ToObject(propertyType, value);</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> }</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> }</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> }</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> }</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> }</div> <div style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow-x: hidden; overflow-y: hidden;" id="_mcePaste"> }</div>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);
}
}
Basically, if the model's property is an enum and the value in the value provider is not, convert it. Throw some extra error handling around this and you've got our code. There's probably a better solution involving getting the correct value in the value provider in the first place, but Reflector doesn't seem to want to run for me so I can't dig into it and this works well enough for now.
------------
If you don't like my comments, that's cool.
Use my code freely, but at your own risk.
And stop telling me to mark answers you greedy so-and-sos!
Blog: Cozened Cognizance
dmose
Member
68 Points
77 Posts
Re: JsonValueProviderFactory
Nov 20, 2010 11:39 PM|LINK
Where did you make this change? I only have Microsoft.Web.Mvc ..where is the source for this?
onevextchuck
Member
2 Points
6 Posts
Re: JsonValueProviderFactory
Nov 23, 2010 11:38 AM|LINK
Here's a summary of the problem and solution.
I ran into this problem when I found a solution to sending JSON to MVC Actions in this post. The post describes the simple steps to hook it up and how to pass JSON to an action. The JsonValueProviderFactory is included in the MVC Futures library.
The problem is that the JsonValueProvider does not create enum values in the value dictionary (it creates ints) and the default model binder doesn't seem to know what to do when ints are provided for enum values. Probably for performance reasons.
The EnumConverterModelBinder code I posted is a custom class I created that checks if the property being bound to is an Enum and the data in the value dictionary is not. In this case only, it converts the dictionary value and returns it to be populated in the model. If those conditions are not met, the default implementation is used. I tried to do this in a way that minimizes the performance impact on all other property types.
You can set the default model binder in Global.asax. This is from memory at the moment so it may not be 100% correct, but in the Application_Start method, add the following line.
Models.DefaultBinder = new EnumConverterModelBinder()
Hope that helps
------------
If you don't like my comments, that's cool.
Use my code freely, but at your own risk.
And stop telling me to mark answers you greedy so-and-sos!
Blog: Cozened Cognizance