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

ASP.NET MVC attribute routing

As we start to add more pages to our ASP.NET MVC section of our application, I’ve found that the MVC routing table in version 4 is somewhat constraining. I’m not alone, it seems others find it awkward in doing more intelligent handling of routes.

Problem

Our site has customers, and we have different services for these customers. I’ve partitioned everything customer-related into it’s own area, so that the base URL for it is /Customer/

We use integer account ID’s from the database as our primary key, so logically if I want to refer to a specific customer I prefer the URL

/Customer/123

as our root. If we followed the standard MVC default, which would be

/Customer/Home/Index/123

Ugh. That just sounds long-winded and over-complicated.

So for example, the home page should be

/Customer/123/Home

The default ASP.NET route won’t work for this of course: /Customer/{controller}/{action}/{id} will interpret this as a controller called 123 and an action Home, with no ID. Easy enough to change the routing as we have until now:

/Customer/{customerID}/{controller}/{action}

First problem with this approach is that customerID is now a required value: what if I want to create a search page, to find a customer? I don’t have a customerID in this context, but it’s still customer related. What I’d like is a URL like

/Customer/Search

While this is possible in the MVC4 routing engine, it leads to a longer and longer set of routes to be registered in our RegisterRoutes method. What we actually want to specific is some kind of pattern matching a la regex, e.g.

/Customer/{CustomerID:\d+}/{controller}/{action}
/Customer/{controller:[^\d*]}/{action}

Attribute Based Routing

Alas the current MVC4 routing engine does not support this. However there is an alternative that it seems the ASP.NET team is adopting to appear in MVC 5: attribute based routing(ABR).

Installing is a breeze: Install the Nuget package it adds a setup file to the App_Start folder which loads the configuration option. That’s it.

Each controller/action can then decorated with attributes which define the routing. Using my example above, all my controllers live in the Customer area. However, I generally want to prefix each route/action with the customerID. In ABR, I can define the area and a prefix at the controller level:

    [RouteArea("Customer")]
    [RoutePrefix("{CustomerID:int}/Issue")]
    public class IssueController : Controller

In the above sample, the URL

~/Customer/{CustomerID}/Issue

is now the base URL for all actions on this controller. ABR will extract and parse the customer ID as an integer from the URL, and I can use it directly in my action methods, e.g.

        [GET("")]
        [GET("List")]
        public ActionResult List(int CustomerID)
        {
            return View();
        }

The List action works with a blank or “List” URL part, so either of the following routes will map to it:

~/Customer/{CustomerID}/Issue

~/Customer/{CustomerID}/Issue/List

No More IDs

When I refer to a specific Issue by ID, I can avoid the generic ‘{ID}’ we get in the old routing engine:

        // GET: /Customer/{CustomerID}/Issue/{IssueID}
        [GET("{IssueID:int}")]
        public ActionResult Index(int CustomerID, int IssueID)
        {

Now let’s say I want my search page, which does not have a customer ID:

~/Customer/Search

We can define a Search controller by dropping the CustomerID part:

    [RouteArea("Customer")]
    [RoutePrefix("Search")]
    public class SearchController : Controller
    {

This will map to the URL

~/Customer/Search

I won’t go into the ABR specification and all the options it provides, as the library is well documented and logical.

A big thanks to Tim McCall for his work!

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;
            }
        }

    
    }
}

Controller is Missing from “Add New Item” Dialog in Visual Studio 2012

We upgraded an ASP.NET 4.0 WebForms application to use MVC4 – but the “Add New Item” dialog didn’t list Controller in the list of web file types.

After a bit of research and using a file compare tool, I found this is controlled by the “ProjectGuidTypes” setting in the .csproj file.

An MVC web project will have the {E3E379DF-F4C6-4180-9B81-6769533ABE47} type in this setting. If your project isn’t showing this option, try adding this to the setting, e.g. :

<ProjectTypeGuids>{E3E379DF-F4C6-4180-9B81-6769533ABE47};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

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.

Give it a REST please

I attended DDD10 this weekend and among the presentations was one by Jacob Reimers called Taking REST beyond the pretty URL.

Jacob’s Case

Jacob’s contention here was that the examples of MVC that use actions in the URLs were an improper use of a REST approach.

His "’bad’ example was a simple bookmarks application. The URL /Bookmark lists all bookmarks and /Bookmark/{id} should represent the bookmark resource. Editing a bookmark in the original application was done via /Bookmark/Edit/{id} and there was /Bookmark/Details/{id}

His argument here is that Edit is an action and we should not be doing this via the URL, we should use a HttpPost verb on the same URL. He shows that sending a HttpGet to the MVC URL with “accepts: application/json” should return JSON and not HTML.

Why This Is Wrong

I understand his point but I think that it misses one vital thing:

Data is not an application, and an application is not data.

We are trying to treat a single URLs as both access to a resource and an application.

A good example of how easy it was to generate confusion was that Jacob’s own application treated /Bookmark as “a list of all bookmarks”.

I am not sure if this is a mandated requirement of REST but a “list of bookmarks” is not a “single bookmark”, they are two different things. I realise it’s a common convention but that does not make it correct.

So Jacob’s own application allowed us to inline-edit the bookmark in the URL /Bookmark was incorrect based on his own premise. The URL should have been /Bookmark/{id} to edit a single bookmark, which means REST is saying “the URL /Bookmark can only represent the list and not a single entry.

So this leads to the conclusion that REST requires our application cannot work in a way that differs from the REST constraints. We cannot design a pragmatic application URL structure, it must represent some fixed system. This means the REST conventions are forcing the application designer to follow a fixed convention which in many cases (including Jacob’s own example) is likely to be illogical or unworkable.

Which makes it all but irrelevant.

Getting 404 error calling a HttpDelete method in WebAPI?

I was playing with KnockoutJS and MVC version 4 today, and wanted to call a Web API Delete method to clear out my data on the server.

Although my Get requests to the API controller were working fine and returning JSON data, every time I tried to make an AJAX call with a HttpDelete verb, I got a 404 error.

I even loaded Fiddler and tried posting a DELETE directly to the API, but continued to get a 404 error plus a lot of HTML from IIS about the resource not being found.

After a lot of searching, I found this StackOverflow article which explained the same problem and had two solutions. While both work, the correct approach is to add the following lines to the system.webServer section of web.config:

  <remove name="UrlRoutingModule-4.0" />
  <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />
  <!– any other modules you want to run in MVC e.g. FormsAuthentication, Roles etc. –>
</modules>