Care to elaborate more on that ? I mean it just reads the input stream and passes it to the JavascriptSerializer. Does it do anything else weird ?
–
sirroccoJan 17 '11 at 11:57

2

@sirrocco, it does more than this. Look at the JsonValueProviderFactory with reflector. You will see that it uses the DeserializeObject method instead of Deserialize because at that moment it doesn't know the type of the model. Then it builds a completely new DictionaryValueProvider and as you can see only the MakePropertyKey and MakeArrayKey private functions are implemented which generate the prefix.propertyName and prefix[index] notation. There is nothing that handles the case of a dictionary which need to be of the form prefix[index].Key and prefix[index].Value.
–
Darin DimitrovJan 17 '11 at 12:15

1

So think of it as a bug or an unimplemented feature. As you prefer :-)
–
Darin DimitrovJan 17 '11 at 12:16

2

Worth noting that the above bug is now reported as FIXED at MS Connect. Yay for that.
–
John HargroveJun 12 '11 at 4:35

1

And confirmed - if you upgrade to ASP.Net 4.5 and MVC 4 you can serialize JSON Dictionaries on POST via the default ValueBinders in MVC.
–
Chris MoschiniDec 29 '12 at 1:40

I came across the same issue today and came up with a solution which doesn't require anything but registering a new model binder. It's a bit hacky but hopefully it helps someone.

public class DictionaryModelBinder : IModelBinder
{
/// <summary>
/// Binds the model to a value by using the specified controller context and binding context.
/// </summary>
/// <returns>
/// The bound value.
/// </returns>
/// <param name="controllerContext">The controller context.</param><param name="bindingContext">The binding context.</param>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException("bindingContext");
string modelName = bindingContext.ModelName;
// Create a dictionary to hold the results
IDictionary<string, string> result = new Dictionary<string, string>();
// The ValueProvider property is of type IValueProvider, but it typically holds an object of type ValueProviderCollect
// which is a collection of all the registered value providers.
var providers = bindingContext.ValueProvider as ValueProviderCollection;
if (providers != null)
{
// The DictionaryValueProvider is the once which contains the json values; unfortunately the ChildActionValueProvider and
// RouteDataValueProvider extend DictionaryValueProvider too, so we have to get the provider which contains the
// modelName as a key.
var dictionaryValueProvider = providers
.OfType<DictionaryValueProvider<object>>()
.FirstOrDefault(vp => vp.ContainsPrefix(modelName));
if (dictionaryValueProvider != null)
{
// There's no public property for getting the collection of keys in a value provider. There is however
// a private field we can access with a bit of reflection.
var prefixsFieldInfo = dictionaryValueProvider.GetType().GetField("_prefixes",
BindingFlags.Instance |
BindingFlags.NonPublic);
if (prefixsFieldInfo != null)
{
var prefixes = prefixsFieldInfo.GetValue(dictionaryValueProvider) as HashSet<string>;
if (prefixes != null)
{
// Find all the keys which start with the model name. If the model name is model.DictionaryProperty;
// the keys we're looking for are model.DictionaryProperty.KeyName.
var keys = prefixes.Where(p => p.StartsWith(modelName + "."));
foreach (var key in keys)
{
// With each key, we can extract the value from the value provider. When adding to the dictionary we want to strip
// out the modelName prefix. (+1 for the extra '.')
result.Add(key.Substring(modelName.Length + 1), bindingContext.ValueProvider.GetValue(key).AttemptedValue);
}
return result;
}
}
}
}
return null;
}
}

The binder is registered in the Global.asax file under application_start

Grab the following NuGet package for System.Json, which includes the new JsonValue type. JsonValue is a flexible new JSON representative type that fully supports C# 4 dynamic, and is also an IEnumerable<KeyValuePair<string, JsonValue>> in the event you wish to treat a payload as a dictionary/associative array.