Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

POST a complex viewmodel to a controller

I am trying to grasp the concepts behind Knockout (2.2.0).
Since I am working with ASP.NET MVC 4 I thought I could follow an example which has a "complex" view-model (master-details).

The only thing I could find is MvcMusicStore.

The code is easy to read and I've figured out how to read a view-model from the server and build a view-model on the client.

This is my C# ViewModel:

public class Person
{
    public Person()
    {
        this.Phones = new List<Phone>();
    }
    public string Name { get; set; }
    public string Surname { get; set; }
    public int Age { get; set; }

    public IList<Phone> Phones { get; set; }
}

public class Phone
{
    public string Model { get; set; }
    public string Number { get; set; }
}

My controller returns a populated model to the view, which converts it to a knockout viewmodel:

var data = @Html.Raw(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model));
var model = ko.mapping.fromJS(data);
ko.applyBindings(model);

I've used the same pattern Rob Conery used and the above code is a simplified version.

This is my view:

@using (@Html.BeginForm("Index", "Home", FormMethod.Post))
{
    <input id="Name" name="Name" type="text" data-bind="value: Name" value="" />
    <br />
    <input id="Surname" name="Surname" type="text" data-bind="value: Surname" value="" />
    <br />
    <table>
    <tbody data-bind="foreach: Phones">
        <tr>
            <td><input type="text" data-bind='value: Model' /></td>
            <td><input type="text" data-bind='value: Number' /></td>
        </tr>
    </tbody>
    </table>
    <input type="submit" value="Send" />  
}

Now, I would like to submit my FORM and read the updated view-model on my controller.

To achieve this I've used the same solution found in MvcMusicStore.
If you look at line 49 Rob tries to serialize the FORM:

var data = $("#orderForm").serialize();

and POST to a controller:

$.post("/orders/edit", data, callback);

I don't seem to be able to make this code work with my view-model. My controller:

[HttpPost]
public JsonResult Index(Models.Person viewModel)
{
...
}

don't seem to be able to deserialize the children and if debug the client-side I can see the seralize method cannot serialize them either.

I don't understand why he's trying to serialize the FORM, actually. Since we have a view-model managed by knockout. Isn't there any other way I can serialize a "complex" model like this and POST it to a controller ?

Do you think that solution (MvcMusicStore) is doing things properly or should I follow some other pattern?

Any help would be appreciated.

UPDATE

I think I've found a solution but I have to do some more tests.
If I use the toJSON KO method:

var data = ko.toJSON(model);

I can have the whole viewmodel serialized properly; then I have to POST the data using $.ajax

$.ajax({
    type: 'POST',
    dataType: 'json',
    contentType: 'application/json; charset=utf-8',
    url: "/Home/Index",
    data: dataToSave,
    success: callback,
    error: function (req, status, error) {
       alert("Status: " + status + " Error: " + error);
      }
});

This way everything seem to work fine but, still, I would like to know if there are any recommended ways.

SOLUTION

Michael Berkompas and nemesv have given me useful information and I've put together a simple application, just in case someone else is interested.

like image 857
LeftyX Avatar asked Jan 03 '13 18:01

LeftyX


1 Answers

The problem is that inside the table your input elements don't have name attributes.

You can set the name with the attr binding because MVC requires a special name format when model binding collection, you need to write:

<tbody data-bind="foreach: Phones">
        <tr>
            <td><input type="text" data-bind="value: Model, attr: { name: 'Phones[' + $Index() + '].Model' }" /></td>
            <td><input type="text" data-bind="value: Number, attr: { name: 'Phones[' + $Index() + '].Number' }" /></td>
        </tr>
</tbody>

Or alternatively because you anyway have your viewmodel you can directly send it to your controller:

var model = ko.mapping.fromJS(data);

//...

$.ajax({
    type: 'POST',
    url: "/orders/edit",
    contentType: "application/json",
    data: ko.mapping.toJSON(model),
    success: callback
});

Note that in this case you cannot use $.post because you need to specify the contentTypeas "application/json".

But I think there is no recommended way to do this. Both the $("#orderForm").serialize() (with the proper input names what I mentioned) and the ko.toJSON(model) works because the difference is only how the data gets prepared/encoded for the ajax request.

I think it's more like a personal preference which one to use. In this particular case doing the ko.toJSON(model) way seems better because you don't have to deal with the generation of the special input names for ASP.NET MVC. You can send your viewmodel as it is to the server. And it feels more natural when using knockout.

like image 123
nemesv Avatar answered Dec 29 '22 07:12

nemesv