Wednesday, January 18, 2012

Posting a JavaScript (JSON) dictionary to an ASP.NET MVC controller using a custom model binder

UPDATE 2012-03-26 Also check out the the follow-up on this post.
Yesterday I tried to post a JSON object containing a dictionary to a ASP.NET MVC controller action. I used this (partly jQuery) code:
var data = {
    Stats: {
        "France": 65027000,
        "UK": 62300000,
        "Netherlands": 16715489
    }
};
$.post("save", data);
The controller action method is:
[HttpPost]
public ActionResult SaveStats(StatsModel stats)
{
   // ...
   return View();
}
And the StatsModel class is:
public class StatsModel
{   
    public IDictionary<string, int> Stats { get; set; }
}
Since the the default ASP.NET MVC model binder is quite powerful, I did not expect this to be a problem. However, the Stats dictionary property of the instantiated method parameter was always null.

I then tried to post a plain JSON dictionary to a modified action method:
[HttpPost]
public ActionResult SaveStats(Dictionary<string, int> stats)
{
    // ...
    return View();
}
This time the stats method parameter became null. It appears that the default ASP.NET MVC model binder is unable to handle the JavaScript dictionaries properly, at least in the intuitive (I think) notation that I used.

To solve my problem and as an exercise I decided to write my own custom model binder. The result can be downloaded using the link below. The DictionaryModelBinder can handle both situations described above and must be applied to an action method as follows:
[HttpPost]
public ActionResult SaveStats([ModelBinder(typeof(DictionaryModelBinder))]
    StatsModel stats)
{
    // ...
    return View();
}
or
[HttpPost]
public ActionResult SaveStats([ModelBinder(typeof(DictionaryModelBinder))]
    Dictionary<string, int> stats)
{
    // ...
    return View();
}

3 comments:

  1. Doesn't seem to work when using a json content/type?

    ReplyDelete
    Replies
    1. You're right! My custom model binder does not bind to *real* JSON data, but to JavaScript dictionaries (in JSON notation, if I am correct) that are converted to a list of key/value pairs before being posted. The reason for the conversion is that the default content type for the jQuery Ajax post commands is 'application/x-www-form-urlencoded'. This makes the title of my blog post a little bit misleading.

      When you explicitly set contentType: 'application/json; charset=utf-8' using jQuery's $.ajax my DictionaryModelBinder will not work, since it expects its values from the Request.Form collection. BUT: If I explicitly set the JSON content type, the request headers for my post still contain the following data structure (before URL encoding):
      Stats[France]=65027000&Stats[UK]=62300000&Stats[Netherlands]=16715489

      This is not JSON either. Can anyone explain this? (I have checked the Request payload in the Network > Headers section of Chrome's development tools).

      Delete
    2. Maybe you could combine ExtendedJsonValueProviderFactory with your code? The solution there is a little bit ugly (have to change the model with 'dictionary' suffix, only works with strings) compared to your nice generic binder.

      http://stackoverflow.com/questions/7042919/mvc-3-defaultmodelbinder-cannot-deserialize-net-dictionary-object-passed-to-an

      Delete