Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC3 Complex JSON List Binding

Having issues getting ASP.NET MVC3 to bind my complex JSON object that contains a list.

Here's the structure I have for my objects.

public class PageModel
{
    public PageModel() { }
    public CustomObject1 CustomObject { get; set; }
    public IEnumerable<CustomObject2> Objects { get; set; }

}

public class CustomObject1
{
    public CustomObject1() { }
    [Required]
    public int CustomId1 { get; set; }
    public string CustomName { get; set; }
}

public class CustomObject2
{
    public CustomObject2() { }
    [Required]
    public int Custom2Id { get; set; }
    public CustomObject3 SubItem { get; set; }
    public int SubItemId { get; set; }
}

You can assume CustomObject3 is of similar structure - no need to duplicate yet another made up class, so I figure you can use your imagination :)

Here's the Javascript/Jquery that makes the POST call (assume all the JS leading up to this provides the correct data):

//$obj1 has all data for the first object
var firstObj = { };
firstObj.CustomId1 = $obj1.Id;
firstObj.CustomName = $obj1.Name;

var i = 0;

//$objects is an array with all the data
$.each($objects, function() {
    objsArray[i] = {
    Custom2Id: $(this).Id,
    SubItemId: $(this).itemId
    };

    ++i;
});

$.ajax({
    type: 'POST',
    url: '/Action/Method',
    data: { CustomObject: firstObj, Objects: objsArray },
    //Success/error handlers here
});

And finally (I know, quite some code) here's an overview of the method I have:

public class ActionController : Controller {
    public JsonResult Method(PageModel model) {
        //Gets here - model.CustomObject is filled correctly, and model.Objects has a correct count of whatever data I passed to the method - but all of the properties are empty!
    }
}

As I said, the first object is filled and all of the data is there when I debug and step through. If I pass two objects in the Objects array in the JSON object, I see a Count of 2 in the controller, but Custom2Id and SubItemId are empty. What gives?

When I specify a contentType of 'application/json' in my $.ajax call, MVC complains about the data being passed. Also tried splitting the model parameter in the MVC method into two separate parameters, but it does not help.

Any help is greatly appreciated - this one has me stumped!

like image 391
Mattygabe Avatar asked Mar 19 '12 14:03

Mattygabe


2 Answers

So, the solution to this particular issue is a combination of two or more solutions found elsewhere on SO (one of them possibly being Jakub's answer revolving around the use of a generic collection type - IEnumerable - as opposed to a more concrete one - List).

First, the server side code need not be changed - all is well on that front. All of the changes that need to be made are client side, and specifically how jQuery and your Javascript send the data to said server.

Here's the old Javascript:

$.ajax({
    type: 'POST',
    url: '/Action/Method',
    data: { CustomObject: firstObj, Objects: objsArray },
    //Success/error handlers here
});

First issue here is that the Javascript is not informing the server at '/Action/Method' what exactly you're sending (is it binary, is it a mix of parameters, or is it JSON?) so adding a contentType: 'application/json' line will serve that purpose. When you do this, the server then knows that it must decode JSON...

But as it would stand now, the decoding would fail with an exception being thrown ("Invalid JSON primitive") before the code even enters your method (MVC handles the decoding and binding then). In the AJAX call above, I was building a new JSON object in the $.ajax options, but the JSON being sent wouldn't pass a JSON validator (James Kyburz mentions a great JSON validator: JSONLint Validator). Instead, create the JSON object before the $.ajax call, and stringify the object so it's in a proper JSON format. Altogether, here's what the AJAX call should look like:

var dataObj = { CustomObject: firstObj, Objects: objsArray };

$.ajax({
    type: 'POST',
    url: '/Action/Method',
    contentType: 'application/json',
    data: JSON.stringify(dataObj),
    //Success/error handlers here
});
like image 126
Mattygabe Avatar answered Sep 29 '22 08:09

Mattygabe


Change public IEnumerable<CustomObject2> Objects { get; set; } to List<CustomObject2>

Model binder doesn't know how to create an instance of IEnumerable interface, it needs something more concrete.

You also need to change this:

data: { CustomObject: firstObj, Objects: objsArray }

into

data: { model: { CustomObject: firstObj, Objects: objsArray } }

The model part matches the name of the parameter in your action.

like image 30
Jakub Konecki Avatar answered Sep 29 '22 06:09

Jakub Konecki