Asp.Net MVC 4 Automatically bind model from array of objects in form post

Jimbo picture Jimbo · Apr 23, 2014 · Viewed 10.3k times · Source

I have built an array of objects in JavaScript and want to post them back to the server via Ajax (Im using jQuery)

The JavaScript object array looks like this:

var columns = [
    { name: 'col 1', source: 'whatever', hidden: false, width: 50 },
    ...
];

Im posting it back like this:

$.post('/MyController/MyAction', { 'columns': columns });

On the controller action Im currently getting this:

enter image description here

I have a c# object called JqColumn that I want to bind the post into, it looks like this:

public class JqGridColumn
{
    public string name;
    public string source;
    public int width;
    public bool hidden;
}

So I thought that adding a parameter in the controller action of type JqGridColumn[] columns would automatically bind the posted data, but it doesn't (it generates a array, with the correct number of elements, but each item in the array has blank values)

Can anyone tell me what Im doing wrong? Thanks!

UPDATE

At present, I am manually binding the items in my controller action as follows:

    public void ColumnChooser(JqGridColumn[] columns)
    {
        for (int i = 0; i < columns.Length; i++)
        {
            columns[i].hidden = bool.Parse(Request.Form["columns[" + i + "][hidden]"]);
            columns[i].width = int.Parse(Request.Form["columns[" + i + "][width]"]);
            columns[i].name = Request.Form["columns[" + i + "][name]"];
            columns[i].source = Request.Form["columns[" + i + "][source]"];
        }
        return;
    }

...which works fine, but I'd really like to know the .Net MVC (correct) way to do it!

Answer

haim770 picture haim770 · Apr 24, 2014

Since you didn't register a specific ModelBinder for the JqGridColumn type, the DefaultModelBinder will be used. But:

  • It won't bind Fields, only public Properties.

  • The expected format for Array binding is columns[0].name while you're actually posting columns[0][name].

The problem could be solved easily if you'll simply send your columns in JSON format instead of Name-Value-Pairs:

$.ajax({
    url: '/MyController/MyAction',
    method: 'POST',
    contentType: 'application/json',
    data: JSON.stringify({ columns: columns })
});

Yet, if you don't like to change your class, you could register a ModelBinder specific for JqGridColumn and have it working even with Fields and current jQuery serialization:

public class JqGridColumnBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        string name = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "[name]").AttemptedValue;
        string source = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "[source]").AttemptedValue;
        int width = (int)bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "[width]").ConvertTo(typeof(int));
        bool hidden = (bool)bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "[hidden]").ConvertTo(typeof(bool));

        return new JqGridColumn
        {
            name = name,
            source = source,
            width = width,
            hidden = hidden
        };
    }
}

Then register it in App_Start/ModelBindingConfig.cs:

binders.Add(typeof(JqGridColumn), new JqGridColumnBinder());