3

I am using MVC4 and trying knockoutjs library with it. The form submits the data to the controller if I use the traditional Submit click without using Knockoutjs library. I am using knockoutjs mapping plugin to convert the viewmodel from the server to create a client-side viewmodel and then trying to extend it client-side.

  • To convert from server-side to client-side vm, I'm using ko.mapping.fromJS(model);
  • To post the data back to the server, I'm converting it back by ko.toJSON(model) while sending via ajax using jQuery.

The data that I receive on server is null. Also, when I log ko.toJSON(model) in console, I get the following:

{
    "FirstName": "foo",
    "LastName": "foo1",
    "Address": "United Kingdom",
    "Age": 22,
    "__ko_mapping__": {
        "ignore": [],
        "include": ["_destroy"],
        "copy": [],
        "observe": [],
        "mappedProperties": {
            "FirstName": true,
            "LastName": true,
            "Address": true,
            "Age": true
        },
        "copiedProperties": {}
    }
}

It seems I am not doing right while converting the js object back to json format to send the data to the server. Below is all my code:

Controller Code:

public class PersonController : Controller
    {
        PersonViewModel viewModel = new PersonViewModel();
        //
        // GET: /Person/
        [HttpGet]
        public ActionResult Index()
        {

            return View(viewModel);
        }

        [HttpPost]
        public ActionResult Index(PersonViewModel viewModel)
        {
            if (ModelState.IsValid)
            {

            }
            return View(viewModel);
        }
//
    // GET: /Person/LoadData/

    public ActionResult LoadData()
    {
        viewModel = new PersonViewModel() { FirstName = "foo", LastName = "foo1", Age = 22, Address = "United Kingdom" };
        return Json(viewModel, JsonRequestBehavior.AllowGet);
    }
}

ViewModel at Server:

 public class PersonViewModel
    {
        [Required]
        public string FirstName { get; set; }
        [Required]        
        public string LastName { get; set; }
        [Required]
        public string Address { get; set; }
        [Required]
        public int Age { get; set; }
    }

ViewModel at client-side - JavaScript :

var Person = function () {
    var self = this;


    self.SetupKOBindings = function () {
        var source = null;
        this.GetViewModelFromServer = function () {
            $.ajax(
                {
                    url: "/Person/LoadData",
                    type: "GET",
                    async: false
                }).
            success(function (data) {
                source = data;
            });
        }();
        return ko.mapping.fromJS(source);
    };

    self.ViewModel = function () {
        this.model = self.SetupKOBindings();

        this.model.Save = function () {
            console.log(ko.toJSON(model));
            $.ajax(
               {
                   url: "/Person/Index",
                   type: "POST",
                   data: ko.toJSON(model),
                   async: true
               }).
           success(function (data) {

           });
        }

        return this.model;
    };

    self.ApplyKOBindings = function (vm) {
        ko.applyBindings(vm);
    };

    return this;
};

$(function () {
    var PersonPage = Person();
    var viewModel = PersonPage.ViewModel();
    PersonPage.ApplyKOBindings(viewModel);
});

HTML in .cshtml at server:

<form action="" method="post">
        <div>
            First Name:
            @Html.TextBoxFor(model => model.FirstName, new { data_bind = "value: FirstName,valueUpdate:['afterkeydown','propertychange','input']" })<br />
        </div>
        <div>
            Last Name:
                    @Html.TextBoxFor(model => model.LastName, new { data_bind = "value: LastName,valueUpdate:['afterkeydown','propertychange','input']" })<br />
        </div>
        <div>
            Address:
                    @Html.TextBoxFor(model => model.Address, new { data_bind = "value: Address" })<br />
        </div>
        <div>
            Age:
                    @Html.TextBoxFor(model => model.Age, new { data_bind = "value: Age" })<br />
        </div>
        <input type="submit" value="Save" data-bind="click: Save"/>

    </form>

3 Answers 3

6

If you are using the mapping plugin you should use ko.mapping.toJSON(model) instead of ko.toJSON(model) because this removes "ko_mapping" properties.

But your main problem is that because you are sending JSON you need to tell in your request that you are sending JSON otherwise ASP.NET MVC cannot parse the request on the server side. For this you need to set the contentType property to "application/json":

So the following $.ajax call should work:

$.ajax({
    url: "/Person/Index",
    type: "POST",
    data: ko.mapping.toJSON(model),
    async: true,
    contentType: "application/json"
}).success(function (data) {

});
Sign up to request clarification or add additional context in comments.

2 Comments

Hey your solution works. thanks. But I always thought that jquery handles this automatically and also the same with mvc model binder. Is this not true?
No Jquery does not do this for you. The default contentType is 'application/x-www-form-urlencoded` which is not working in your case because you are using toJSON. It would work without the "application/json", if you would write toJS so ko.mapping.toJS(model) but it won't if you have arrays in your viewmodel. So you are always better with the toJSON and contentType: "application/json" setup.
0

If you use ko.mapping.fromJS to convert a js object into an object with observables.

You have to use ko.mapping.toJS to convert it back into js object.

I hope it helps.

2 Comments

he's calling toJSON which internally calls toJS
@Damien toJSON also calls browser's native stringify method. I am able to hit the post action at server-side that means its able to find the action which accepts personvm.
0

Your Controller has no idea what variable is being sent through and thus no idea what to bind to.

Change:

this.model.Save = function () {
            console.log(ko.toJSON(model));
            $.ajax(
               {
                   url: "/Person/Index",
                   type: "POST",
                   data: ko.toJSON(model),
                   async: true
               }).
           success(function (data) {

           });
        }

To:

this.model.Save = function () {
            console.log(ko.toJSON(model));
            $.ajax(
               {
                   url: "/Person/Index",
                   type: "POST",
                   data: { viewModel: ko.toJSON(model) },
                   async: true
               }).
           success(function (data) {

           });
        }

Update (something to try):

var data = {
    FirstName: model.FirstName || 'Test'
};

$.ajax({
           url: "/Person/Index",
           type: "POST",
           data: ko.toJSON(data),
           async: true
       }).
       success(function (data) {
  });

2 Comments

already tried this before. null is what I see in debugger mode & it doesn't even show the 4 properties. If using data directly with ko.toJSON(model), I can see the 4 properties of viewmodel which is null.
Updated with something else to try - if that works then you'll have more of an idea what's going on.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.