POSTing Knockout JSON data to WebAPI HttpPost methods

This article describes how to get JSON data from JavaScript to the server, using a WebAPI HttpPost method, with strongly-typed .NET values converted from the JSON data.

Why?

I decided to write this article as there isn’t much clarity on the Internet (shock!) on this subject. There are endless articles on WebAPI and using it with KnockoutJS to GET data. This is simple because WebAPI turns your .NET objects into the requested format (JSON, XML etc.).

What isn’t that simple is posting data back to a WebAPI method via HttpPost. There are lots of little things to do (or not do) to make it work. I’m sharing this so you can learn from our experiments!

ViewModel

Let’s say I have a Knockout viewmodel that represents a shopping cart, so we have something everyone will recognise. We have an array of Items, and a shipping method in the cart.

var Cart = (function () {
    function Cart() {
        this.Items = ko.observableArray([]);
        this.ShippingType = ko.observable("Standard");
        this.Items([
            { Code: 1, Name: "Apple" }, 
            { Code: 2, Name: "Banana" }
        ]);
        this.ShippingType("Next-Day");
    }
    Cart.prototype.SaveCart = function () {
        var data = ko.toJSON(this);
        $.ajax({
            url: "/api/Cart/Save",
            type: "POST",
            data: data,
            datatype: "json",
            processData: false,
            contentType: "application/json; charset=utf-8",
            success: function (result) {
                alert(result);
            }
        });
    };
    return Cart;
})();

We can ignore the Html side, just assume there is a “Save” button that calls the SaveCart method, and this will make an AJAX HttpPost to the /api/Cart/Save method.

In the example above I’ve loaded a couple of items into the array and changed the ShippingType. Normally we’d load all this from outside the viewmodel.

So what are the key things to note here?

1) Don’t Use $.post

The first thing we learned is don’t use jQuery’s $.post() function. This will set the content type as application/x-www-form-urlencoded, but this is not the case: we are sending JSON data.

Instead you should use the lower-level $.ajax() function, which allows you to customise the request.

2) Don’t Send the ViewModel

Note that we do not send the viewmodel object directly: the properties Items and ShippingType are Knockout functions, not values. We need to unwrap these to get the values.

Fortunately Knockout has this solved: if you call ko.toJSON() it will unwrap any observable values into a plain JSON object.

3) Don’t ‘ProcessData’ and Specify ContentType

We need to tell WebAPI the nature of the body content. We set two important values:

processData: false,

contentType:
"application/json; charset=utf-8",

processData tells jQuery not to encode the data as form-urlencoded, which is the default action. Instead it sends up the JSON.

contentType specifies that the data is JSON. It’s this that WebAPI reads to be able to figure out what it’s getting, and how to read it.

Finally you can specify dataType to specify what the POST request will return.

 

4) Ensure WebAPI Has a Matching Signature

So now it’s time to switch to our server and see how we ensure we handle the data correctly.


public class CartController : ApiController
    {
        [HttpPost]
        public string Save(MyCart cartData)
        {
            // TODO: save
            return (cartData.Items.Count() == 0 ?
                "No items found" : 
                string.Format("I saved {0} items", cartData.Items.Count());
        }
    }

    // properties match our viewmodel
    public class MyCart
    {
        public List<CartItem>[] Items { get; set; }
        public string ShippingType { get; set; }
    }

    // same signature as our
    public class CartItem
    {
        public int Code { get; set; }
        public string Name { get; set; }
    }

Note how we have not made any mention of JSON whatsoever. The MVC ModelBinder will try it’s best to interpret the data it gets, and bind it to the data in your method.

Our Save method takes a single parameter, of type MyCart . The name of the type is not important; what is key is that the properties match the JSON data we are sending. This allows the ModelBinder to convert the incoming data into our .NET object.

Note that I specified a List<CartItem> for the item array: I could have specified CartItem[] array type as well – both are okay and ModelBinder will fill it up for you, provided the properties match up.

5) ModelBinder Tries Hard!

ModelBinder is very tolerant. If your capitalisation is different from the JSON it will use a matching one if it can. So changing MyCart.ShippingType to MyCart.shippingtype won’t cause it to stop working. Well done ModelBinder. However, if you specify both variations, ModelBinder will use the one that exactly matches the JSON name.

Order is not critical: you don’t have to ensure the order of properties is the same in your .NET class, ModelBinder works with names, not the order of them.

6) Fiddler Is Your Friend!

Whenever you are debugging these sort of actions, you simply have to have Fiddler about. It allows you to see what is going back and forth, and if it’s been mangled, transformed or translated.