Rants and Raves

Thanks for visiting my blog!

The Case of the ModelBinding Failure
The Case of the ModelBinding Failure
December 19, 2011

I wasted an evening last night on a simple bug of mine. I was writing a simple HTML data entry page. I was using JSON + $.ajax to POST data to a ASP.NET MVC controller and it used to work. But for the life of me I couldn’t figure out what was wrong. Let’s start with some background.

In ASP.NET MVC3, they made a change to make the JsonValueProviderFactory part of the ValueProviderFactories so that data that comes in as JSON can be mapped to model classes automatically. See this Phil Haack post for how that works:

Essentially the JsonValueProviderFactory is used to match the shape of JSON inputs to model classes.  Therefore if you have a piece of JSON that looks like:

{  
  "id": 1,
  "name": "Some Picture",
  "picture": "http://wilderminds.blob.core.windows.net/img/turninghead.gif"
};

And you have an action that looks like this:

[HttpPost]
public JsonResult SaveImage(ImageModel picture)
{
  if (picture == null)
  {
    return Json(new { success = false, message = "Model came through as null" });
  }

  return Json(new { success = true });
}

The JsonValueProviderFactory’s job is to map the JSON to the ImageModel class:

public class ImageModel
{
  public int Id { get; set; }
  public string Name { get; set; }
  public string Picture { get; set; }
}

It does this by looking at the shape of the JSON and mapping it to properties in the model class. Simple and elegant. Works great…except when it doesn’t.

So how did I lose a full evening to this simple mapping? There are things that can go wrong like not having the routes correct or having bad data sent in the request, but I was reaching the action and my ImageModel was null. No error, no debugging in the output window…it just didn’t work. I stared at this code and cajoled people on twitter to help to no luck. It had to be that the JSON I was sending was incompatible, but I didn’t see how. Fiddler to the rescue.

So I fired up Fiddler2 (www.fiddler2.com) and executed my javascript code that looks like this:

$(document).ready(function () {
  $("#send-data").click(function () {
    $("#status").text("Saving...");

    // Some data in the right shape for the server
    var data = {
      "id": 1,
      "name": "Some Picture",
      "picture": "http://wilderminds.blob.core.windows.net/img/turninghead.gif"
    };

    $.ajax({
      // URI for POST
      url: '/Home/SaveImage',
      // Turn object into JSON
      data: JSON.stringify(data),
      contentType: 'application/json; charset=utf-8',
      type: 'POST',
      success: function (response) {
        if (response.success) {
          // It worked!
          $("#status").text("Saved...");
        } else {
          // 
          $("#status").text(response.message);
        }
      },
      error: function (response) {
        $("#status").text("Error!");
      }
    });
  });
});

Fiddler captured the traffic and showed what I expected (I can use the inspectors to see both the request and the response as shown here):

fiddler1

You can see the session in the Web Sessions pane. And using the Inspectors, I can see the JSON I sent and the JSON I received (remember on failure, I’m returning a JSON result that explains the failure). Fiddlers let’s you use a tool called the “Composer” that will allow you to create a request and run it directly from Fiddler:

fiddler2

While you could craft a custom request in the composer by just typing all the necessary bits, it also lets you drag a request from the Web Sessions pane to clone it.  I cloned the bad request (shown here) and if I press “Execute” it will re-run the request:

fiddler3

Just like the captured request, my re-issuing of the identical request resulted in the model being null.  I suspect it has something to do with the property matching in the ModelBinding so to debug this I just removed properties in the JSON request until it worked. Fiddler allows me to use the Composer to just hand-edit the request so I can re-test quickly:

fiddler4

I hand-edit the JSON in the request and remove the picture property. Once I do that (or another part of the JSON request) I can hit execute to see if it still fails:

fiddler5

It worked…huh? Why would removing the picture property make it fail? If we look at the ImageModel class, the property name isn’t misnamed or anything. The solution was obvious once I knew it was the picture property. Let’s see that action again:

[HttpPost]
public JsonResult SaveImage(ImageModel picture)
{
  if (picture == null)
  {
    return Json(new { success = false, message = "Model came through as null" });
  }

  return Json(new { success = true });
}

The problem is that the name of parameter into the method is picture and MVC is getting confused between wanting to map the picture property of the JSON and the picture parameter of the action. This happens because it would have also mapped our JSON to an action that looked like this perfectly well:

[HttpPost]
public JsonResult SaveImage(int id, string name, string picture)
{

ASP.NET MVC tries to do the right thing and the duplication of the parameter name as the property name got it confused. So to fix this, I simply needed to rename the parameter like so:

[HttpPost]
public JsonResult SaveImage(ImageModel image)
{
  if (image == null)
  {
    return Json(new { success = false, message = "Model came through as null" });
  }

  return Json(new { success = true });
}

Sure, a wasted night (and the resulting loss of time documenting it here so that you can prop up my fragile ego), but it helped me understand what was really going on under the covers.