RequireJS, TypeScript and Knockout Components

Despite the teething problems I had getting my head around RequireJS, I had another go this week on sorting this out. The motivation for this was Knockout Components – re-usable asynchronous web components that work with Knockout. So this article details the steps required to make this work on a vanilla ASP.NET MVC website.

Components

If you’re not familiar with components, they are similar to ASP.NET Web Controls – re-usable, modular components that are loosely coupled – but all using client-side JS and HTML.

Although you can use and implement components without an asynchronous module loader, it makes much more sense to do so and to modularise your code and templates. This means you can specify the JS code and or HTML template is only loaded at runtime, asynchronously, and on-demand.

Demo Project

To show how to apply this to a project, I’ve created a GitHub repository with a web app. Step one was to create the ASP.NET MVC application. I used .NET 4.5 and MVC version 5 for this but older versions would work just as well.

Next I upgraded all the default Nuget packages including JQuery so that we have the latest code, and then amended the home page to remove the standard ASP.NET project home page. So far, nothing new.

RequireJS

Next step is to add RequireJS support. At present our app loads the JavaScript modules synchronously from the HTML pages, using the ASP.NET bundler.

        public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js"));

These are inserted via the _Layout.cshtml template:

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)

First we add RequireJS to the web app from the Nuget project, and the Text Addon for RequireJS. This is required to allow us to load non-JavaScript items (e.g. CSS and HTML) using RequireJS. We’ll need that to load HTML templates. These will load into the /Scripts folder.

Configuring RequireJS

Next, we create a configuration script file for RequireJS. Mine looks like this, yours may be different depending on how your scripts are structured:

require.config({
    baseUrl: "/Scripts/",
    paths: {
        jquery: "jquery-2.1.1.min",
        bootstrap: "bootstrap.min"
    },
    shim: {
        "bootstrap": ["jquery"]
    }
});

This configuration uses the default Scripts folder, and maps the module ‘jquery’ to the version of JQuery we have updated to. We’ve also mapped ‘bootstrap’ to the minified version of the Bootstrap file, and added a shim that tells RequireJS that we need to load JQuery first if we use Bootstrap.

I created this file in /Scripts/require/config.ts  using TypeScript. At this point TypeScript is flagging an error saying that require is not defined. So now we need to add some TypeScript definition files. The best resource for these is the Github project Definitely Typed, and these are all on Nuget to make it even easier. We can do this from the Package Manager console:

   1: Install-Package jquery.TypeScript.DefinitelyTyped

   2: Install-Package bootstrap.TypeScript.DefinitelyTyped

   3: Install-Package requirejs.TypeScript.DefinitelyTyped

Implementing RequireJS

At this point we have added the scripts but not actually changed our application to use RequireJS. To do this, we open the _Layout.cshtml file, and change the script segment to read as follows:

    <script src="~/Scripts/require.js"></script>
    <script src="~/Scripts/require/config.js"></script>
    @* load JQuery and Bootstrap *@
    <script>
        require(["jquery", "bootstrap"]);
    </script>
    @RenderSection("scripts", required: false)

This segment loads require.js first, then runs the config.js file which configures RequireJS.

Important: Do not use the data-main attribute to load the configuration as I originally did –  you’ll find that you cannot guarantee that RequireJS is properly configured before the require([…]) method is called. If the page loads with errors, e.g. if the browser tries to load /Scripts/jquery.js or /Scripts/bootstrap.js then you’ve got it wrong.

Check the network load for 404 errors in the browser developer tools.

Adding Knockout

We add Knockout version 3.2 from the package manager along with the TypeScript definitions as follows:

install-package knockoutjs
Install-Package knockout.TypeScript.DefinitelyTyped

You need at least version 3.2 to get the Component support.

I then modified the configuration file to add a mapping for knockout:

require.config({ baseUrl: "/Scripts/", paths: { jquery: "jquery-2.1.1.min", bootstrap: "bootstrap.min", knockout: "knockout-3.2.0" }, shim: { "bootstrap": ["jquery"] } });

Our last change is to change the TypeScript compilation settings to create AMD output instead of the normal JavaScript they generate. We do this using the WebApp properties page:

image

This allows us to make us of require via the import and export keywords in our TypeScript code.

We are now ready to create some components!

Demo1 – Our First Component: ‘click-to-edit’

I am not going to explain how components work, as the Knockout website does an excellent job of this as do Steve Sanderson’s videos and Ryan Niemeyer’s blog.

Instead we will create a simple page with a view model with an editable first and last name. These are the steps we take:

  1. Add a new ActionMethod to the Home controller called Demo1
  2. Add a view with the same name
  3. Create a simple HTML form, and add a script section as follows:
    <p>This demonstrates a simple viewmodel bound with Knockout, and uses a Knockout component to handle editing of the values.</p>
    <form role="form">
        <label>First Name:</label>
        <input type="text" class="form-control" placeholder="" data-bind="value: FirstName" />
        @* echo the value to show databinding working *@
        <p class="help-block">
            Entered: {<span data-bind="text: FirstName"></span>}
        </p>
        <button data-bind="click: Submit">Submit</button>
    </form>
    
    @section scripts {
        <script>
            require(["views/Demo1"]);
        </script>
    }
  4. We then add a folder views to the Scripts folder, and create a Demo1.ts TypeScript file.
// we need knockout
import ko = require("knockout");

export class Demo1ViewModel {
    FirstName = ko.observable<string>();

    Submit() {
        var name = this.FirstName();
        if (name)
            alert("Hello " + this.FirstName());
        else
            alert("Please provide a name");
    }
}

ko.applyBindings(new Demo1ViewModel());

This is a simple viewmodel but demonstrates using RequireJS via an import statement. If you use a network log in the browser development tools, you’ll notice that the demo1.js script is loaded asynchronously after the page has finished loading.

So far so good, but now to add a component. We’ll create a click-to-edit component where we show a text observable as a label that the user has to click to be able to edit.

Click to Edit

Our component will show the current value as a span control, but if the user clicks this, it changes to show an input box, with Save and Cancel buttons. If the user edits the value then clicks cancel, the changes are not saved.

image view mode and  image edit mode

To create this we’ll add three files in a components subfolder of Scripts:

  • click-to-edit.html – the template
  • click-to-edit.ts – the viewmodel
  • click-to-edit-register.ts – registers the component

The template and viewmodel should be simple enough if you’re familiar with knockout: we have a viewMode observable that is true if in view mode, and false if editing. Next we have value – an observable string that we will point back to the referenced value. We’ll also have a newValue observable that binds to the input box, so we only change value when the user clicks Save.

TypeScript note: I found that TypeScript helpfully removes ‘unused’ imports from the compiled code, so if you don’t use the resulting clickToEdit object in the code, it gets removed from the resulting JavaScript output. I added the line var tmp = clickToEdit; to force it to be included.

Registering and Using the Component

To use the component in our view, we need to register it, then we use it in the HTML.

Registering we can do via import clickToEdit = require(“click-to-edit-register”); at the top of our Demo1 view model script. The registration script is pretty short and looks like this:

import ko = require("knockout");

// register the component
ko.components.register("click-to-edit", {
    viewModel: { require: "components/click-to-edit" },
    template: { require: "text!components/click-to-edit.html" }
});

The .register() method has several different ways of being used. This version is the fully-modular version where the script and HTML templates are loaded via the module loader. Note that the viewmodel script has to return a function that is called by Knockout to create the viewmodel.

TypeScript note: the viewmodel code for the component defines a class, but to work with Knockout components the script has to return a function that is used to instantiate the viewmodel, in the same form as this script. To do this, I added a line  return ClickToEditViewModel; at the end of this module. This appears to return the class, which of course is actually a function that is the constructor. This function takes a params parameter that should have a value property that is the observable we want to edit.

 

Using the component is easy: we use the name that we used when it was registered (click-to-edit) as if it were a valid HTML tag.

<label>First Name:</label>
    <div class="form-group">
        <click-to-edit params="value: FirstName"></click-to-edit>
    </div>

We use the params attribute to pass the observable value through to the component. When the component changes the value, you will see this reflected in the page’s model.

Sequence of Events

It’s interesting to follow through what happens in sequence:

  1. We navigate to the web URL /Home/Demo1, which maps to the controller action Demo1
  2. The view is returned, which only references one script “views/Demo1” using require()
  3. RequireJS loads the Demo1.js script after the page has finished loading
  4. This script references knockout and click-to-edit-register, which are both loaded before the rest of the script executes
  5. The viewmodel binds using applyBindings(). Knockout looks for registered components and finds the <click-to-edit> tags in our view, so it makes requests to RequireJS to load the viewModel script and the HTML template.
  6. When both have been loaded, the find binding is completed.

It’s interesting to watch the network graph of the load:

image

The purple vertical line represents when the page finished loading, after about 170ms – about half way through the page being completed and ready. In this case I had cleared the cache so everything was loaded fresh. The initial page load only has just over 33KB of data, whereas the total loaded was 306KB. This really helps make sites more responsive on a first load.

Another feature of Knockout components is that they are dynamically loaded only when they are used. If I had a component used for items in an observable array, and the array was empty, then the components’ template and viewModel would not be used. This is really great if you’re creating Single Page Applications.

Re-using the Component

One of the biggest benefits of components is reuse. We can now extend our viewModel in the page to add a LastName property, and use the click-to-edit component in the form again:

    <label>First Name:</label>
    <div class="form-group">
        <click-to-edit params="value: FirstName"></click-to-edit>
    </div>
    <label>Last Name:</label>
    <div class="form-group">
        <click-to-edit params="value: LastName"></click-to-edit>
    </div>

Now the page has two values using the same control, independently bound.

Advertisements

Async File Uploads with MVC, WebAPI and Bootstrap

This is a ‘how-to’ aide-memoire on my research on file uploading within MVC/client applications.

Our application has a form with a couple of fields and we want to allow the user to upload a file as part of the form submission. Uploading a file in the old WebForm days was pretty simple. But now we want asynchronous uploads and send via WebAPI, so we need to handle things better.

My requirements are as follows:

  1. We don’t want a traditional post-and-redirect model. It must use an async upload method
  2. We need the whole form (file+form fields) in one go: file-only uploads are of no use in this case
  3. We might want KnockoutJS integration if appropriate..
  4. We don’t want the uploaded data stored in files on the webserver: we want memory streams written to our database object
  5. Will be on a Bootstrap page so ideally needs to blend in (although Bootstrap 2.3 does not itself render these controls)

Server-Side

Initial Investigation

There are two parts to the operation: server-side and client-side.

A bit of Googling for the server-side gets us to a blog post by Henrik Nielsen (from 2012):

Asynchronous File Upload using ASP.NET Web API. This ticks two of our boxes:  first it uses WebAPI to handle the post, and secondly it does so asynchronously. It’s not Knockout-bound but that’s not such a big issue at this stage. The example he provides runs as a console app (!) self-hosted web server, so it’s a bit.. interesting.

Except it won’t work. It’s an old beta-version sample with no complete version and it’s missing a lot of using directives, and.. the list goes on.

There are other versions of this around and most of them either don’t work or store the uploads to files, which is contrary to requirements, and not something I’d ever do. I needed something that used memory or streams.

Knockout

I also found a Knockout solution at Khayrov’s Github project. This was a great tool using Knockout to upload files on the client – and in theory then post onward to the server. The only problem was it used the FileAPI which won’t work on most older browsers. Strictly speaking KO isn’t needed for posting but it helps if the form has behaviours on it.

Back to WebAPI and Memory-only

Much more Googling and I found MultipartMemoryStreamProvider which reads the form data into memory. One issue here is it treats normal form values and files the same, so you have to differentiate. A quick Google suggests a solution. This looks like a much better approach, and uses async methods. I amended the code to make it easier to get the files out directly, by parsing the HttpContent list into a list of files. I have attached a source listing at the end of this article, along with a sample Post method that uses this.

Client Side

That seems to take care of the server side. The client side for testing was a standard html form and a submit method, which is not very async. I needed to use a jQuery .ajax call to post the form. Posting forms via AJAX with file uploads isn’t supported directly with .ajax, but after several experiments I settled on the jQuery.Form plugin. This seems to work very well on the versions I tested: IE8, IE10, Chrome v28, and Firefox v22.

I did have a couple of errors on Firefox but that was because I tried to upload a 22MB file as a test: IIS was set to accept the default maximum upload size of 4096K, which is only 4MB. Setting maxRequestLength to a higher value fixed this:

<configuration>

  <system.web>

    <httpRuntime maxRequestLength="32768" />

  </system.web>

</configuration>

The only thing the form plugin does not do well is handle errors: there seems to be no error code available in the .ajax model. We can handle an error event from the jQuery ajax call, but I wasn’t able to determine which error code was being returned.

Binding the Form Values to the Model

As we are processing the multi-part form ourselves, we are getting a list of file and a list of form values. This means we have to manually process the values. We could extract one-by-one using the name, but we should really use a Model Binder..

It does not seem to be possible to use the DefaultModelBinder from MVC as this takes controller contexts as the constructors, so I added a simple one to the provider. This uses a simple reflection-based name match and set operation.

Source

Source code of the amended WebAPI post and supporting classes:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Mvc;

namespace MyWebApp.Api
{
    public class UploadController : ApiController
    {

        /// 
        /// Accept form post with files
        /// 
        /// 
        [System.Web.Mvc.HttpPost]
        public async Task Post()
        {
            if (!Request.Content.IsMimeMultipartContent("form-data"))
                throw new HttpResponseException(HttpStatusCode.BadRequest);

            var provider = await Request.Content.ReadAsMultipartAsync(new InMemoryMultipartFormDataStreamProvider());

            //access form data
            FormCollection formData = provider.FormData;

            //access files
            IList fileContentList = provider.Files;

            var fileDataList = provider.GetFiles();

            // get the files 
            var files = await fileDataList;

            // formulate the response
            if (files.Any())
                return string.Join("; ", 
                    (from f in files 
                     select string.Format("uploaded: {0} ({1} bytes = '{2}')", f.FileName, f.Size, f.ContentType)).ToArray());
            else
                return "No files attached";
        }

       

        public class InMemoryMultipartFormDataStreamProvider : MultipartStreamProvider
        {
            private FormCollection _formData = new FormCollection();
            private List _fileContents = new List();

            // Set of indexes of which HttpContents we designate as form data
            private Collection _isFormData = new Collection();

            /// 
            /// Gets a  of form data passed as part of the multipart form data.
            /// 
            public FormCollection FormData
            {
                get { return _formData; }
            }

            /// 
            /// Gets list of s which contain uploaded files as in-memory representation.
            /// 
            public List Files
            {
                get { return _fileContents; }
            }

            /// 
            /// Convert list of HttpContent items to FileData class task
            /// 
            /// 
            public async Task GetFiles()
            {
                return await Task.WhenAll(Files.Select(f => FileData.ReadFile(f)));
            }

            public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
            {
                // For form data, Content-Disposition header is a requirement
                ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
                if (contentDisposition != null)
                {
                    // We will post process this as form data
                    _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName));

                    return new MemoryStream();
                }

                // If no Content-Disposition header was present.
                throw new InvalidOperationException(string.Format("Did not find required '{0}' header field in MIME multipart body part..", "Content-Disposition"));
            }

            /// 
            /// Read the non-file contents as form data.
            /// 
            /// 
            public override async Task ExecutePostProcessingAsync()
            {
                // Find instances of non-file HttpContents and read them asynchronously
                // to get the string content and then add that as form data
                for (int index = 0; index < Contents.Count; index++)
                {
                    if (_isFormData[index])
                    {
                        HttpContent formContent = Contents[index];
                        // Extract name from Content-Disposition header. We know from earlier that the header is present.
                        ContentDispositionHeaderValue contentDisposition = formContent.Headers.ContentDisposition;
                        string formFieldName = UnquoteToken(contentDisposition.Name) ?? String.Empty;

                        // Read the contents as string data and add to form data
                        string formFieldValue = await formContent.ReadAsStringAsync();
                        FormData.Add(formFieldName, formFieldValue);
                    }
                    else
                    {
                        _fileContents.Add(Contents[index]);
                    }
                }
            }

            /// 
            /// Remove bounding quotes on a token if present
            /// 
            /// Token to unquote.
            /// Unquoted token.
            private static string UnquoteToken(string token)
            {
                if (String.IsNullOrWhiteSpace(token))
                {
                    return token;
                }

                if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)
                {
                    return token.Substring(1, token.Length - 2);
                }

                return token;
            }
        }

        /// 
        /// Class to store attached file info
        /// 
        public class FileData
        {
            public string FileName { get; set; }
            public string ContentType { get; set; }
            public byte[] Data { get; set; }

            public long Size { get { return (Data != null ? Data.LongLength : 0L); } }

            /// 
            /// Create a FileData from HttpContent 
            /// 
            /// 
            /// 
            public static async Task ReadFile(HttpContent file)
            {
                var data = await file.ReadAsByteArrayAsync();
                var result = new FileData()
                {
                    FileName = FixFilename(file.Headers.ContentDisposition.FileName),
                    ContentType = file.Headers.ContentType.ToString(),
                    Data = data
                };
                return result;
            }

            /// 
            /// Amend filenames to remove surrounding quotes and remove path from IE
            /// 
            /// 
            /// 
            private static string FixFilename(string original)
            {
                var result = original.Trim();
                // remove leading and trailing quotes
                if (result.StartsWith("\""))
                    result = result.TrimStart('"').TrimEnd('"');
                // remove full path versions
                if(result.Contains("\\"))
                    // parse out path
                    result = new System.IO.FileInfo(result).Name;

                return result;
            }
        }

    
    }
}

Multiple Knockout ViewModels and Reusable Partial Views

As part of our redesign of our Anvil application to use MVC and Knockout, we needed to figure out how to get reusability with Knockout and partial views.

When constructing a complex form you ideally want to break the problem down into smaller, easier to manage components that are decoupled from the bigger problem. However, you still need to be able to get all these items to interact and work together when you build the complex object.

Binding by Element ID

Knockout has the option of specifying a second parameter in the ko.applyBindings() function that restricts the binding to a given document element (and its contained controls), such as a DIV. Typically examples of this show an element with an ID, and the line, e.g.:

ko.applyBindings(addressVM, document.getElementById("address"));

 

The problem with this approach is that introducing IDs in reusable controls makes them single-instance controls per page. We could add a dynamically generated ID, but then we’d have to link up the correct ID with the correct viewmodel. It gets very messy, very quickly.

There is another issue with this approach: Knockout bindings cannot be nested. If you bind an element to a knockout viewmodel, this element cannot contain another element inside with a different binding. Here is an example, where the address div is inside an order div. We can’t bind this way.

<div id="order">
    <!-- order control bindings -->
    <div id="address">
        <!-- address bindings -->
    </div>
</div>
<
script> var orderVM = new OrderViewModel(); var addressVM = new AddressViewModel(); // this won't work ko.applyBindings(orderVM, document.getElementById("order")); ko.applyBindings(addressVM, document.getElementById("address")); </script>

Alternative Approach and Sample App

Let’s say we want to create an order form. The form will have a Knockout viewmodel for the whole order, but parts of this will be built from reusable components.

We will have a delivery address in the order, but it will be a reusable AddressViewModel. For the actual orders, we will have an observableArray and each order will be an OrderItemViewModel.

Address View Model

First we examine our address. We want reusability, and addresses might be used in several places. So we create a Razor Partial View that lays out the controls and includes the Knockout data-binding attributes thus:

    <table>
        <tr>
            <td>Line 1:</td><td><input data-bind="value: line1" type="text" maxlength="32" /></td>
        </tr>
        <tr>
            <td>Line 2:</td><td><input data-bind="value: line2" type="text" maxlength="32" /></td>
        </tr>
        <tr>
            <td>Line 3:</td><td><input data-bind="value: line3" type="text" maxlength="32" /></td>
        </tr>
        <tr>
            <td>Town:</td><td><input data-bind="value: town" type="text" maxlength="32" /></td>
        </tr>
        <tr>
            <td>Postcode:</td><td><input data-bind="value: postcode" type="text" maxlength="8" /></td>
        </tr>
    </table>

This is a simple control example with no validation or other clever stuff. It just has some controls with data-bind statements.

Note that this has no containing DIV (or other element) with an ID. Also note that there is no mention of scripts, either inline or external. We’ve isolated just the UI element of our control.

By doing this we have made this control reusable: we can put several Address views on a page. We have delegated the script and binding responsibilities to the parent container in which we sit.

With Usage

So the next issue is how to get around the nested viewmodel problem. We have an order view model, which has an address inside it. The solution is the data-binding with: statement, detailed in Ryan Niemeyer’s excellent blog article .

The with statement changes the databinding context from the viewmodel to a part of the view model. So I define our OrderViewModel as follows:

        var OrderViewModel = (function () {
            function OrderViewModel() {
                this.OrderID = ko.observable(0);
                this.CustomerName = ko.observable("");
                this.DeliveryAddress = ko.observable(new Test.AddressViewModel());
                this.InvoiceAddress = ko.observable(new Test.AddressViewModel());
            }
            return OrderViewModel;
        })();

Note that in this example, I’ve created two address entries: DeliveryAddress and InvoiceAddress. Now we can put these in our main view and use the AddressView partial view we created earlier:

<div>
    <!-- this is the order context -->
    <input type="text" data-bind="value: CustomerName" />

    <!-- change context -->
    <div data-bind="with: DeliveryAddress">
        <b>Delivery Address</b>
        @Html.Partial("_AddressView")
    </div>
    <div data-bind="with: InvoiceAddress">
        <b>Delivery Address</b>
        @Html.Partial("_AddressView")
    </div>

</div>

Interaction

The AddressViewModel is independent of the OrderViewModel, but we could add some checks to the order, e.g. checking to see if the address is valid.

We can also cross-bind the two models. Let’s say our order has a DeliveryCharge which is a computed observable, which is based on the country we are delivering to. We want to recalculate this when the country changes. To do this we could subscribe to changes on the Country observable in the Delivery, and then update the DeliveryCharge based on this value.

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.