ZocDoc Engineering

We’re the ZocDoc engineering team, and we love sweet code, dogs, jetpacks, TF2, robots, and lasers... all at once. Read about our adventures here, or apply for a career at ZocDoc!

Client-Server Data Persistence with Backbone.js and .NET MVC

Although originally conceived to sit on top of a Rails backend, Backbone’s RESTful nature makes it easy to use with .NET MVC (or any RESTful backend). This post will explain how to implement client-server data persistence using .NET MVC and Backbone.

Backbone is a fantastic little JavaScript framework for providing MVC-like structure to client-side applications. There are already plenty of resources and examples available that demonstrate how to write an application in Backbone. This example will bypass most of Backbone’s client-side features, and instead focus on binding Models and Collections to a .NET MVC application.

The Interface

Our example app will consist of a list of doctors. Doctors can be added and removed from the list, and their Zociness can be toggled on or off. Backbone assumes a RESTful backend, so we’ll need to write our .NET MVC Controller to handle requests to GET, POST, PUT and DELETE doctors. The interface will look like this:

  • HTTP GET to /zoc/docs
  • HTTP POST to /zoc/docs
  • HTTP PUT to /zoc/docs/id
  • HTTP DELETE to /zoc/docs/id

Creating the Service

We’ll start by creating a Doctor view model. This class defines a doctor and will be shared by .NET and Backbone.

NOTE: Backbone expects all models returned by the server to contain a (case-sensitive) field called id.

public class Doctor
{
    public Guid id { get; set; }
    public string name { get; set; }
    public bool isZocd { get; set; }
}

Next, we’ll create a ZocController with a Docs action. This method retrieves a list of Doctors from a database (using an arbitrary GetDocs method) and returns a JSON serialized response.

public class ZocController : Controller
{
    public ActionResult Docs()
    {
        return Json(GetDocs(), JsonRequestBehavior.AllowGet);
    }
}

With this method in place, an HTTP GET to /zoc/docs might return an example response like this:

[
    {
        "id": "eb12a423-3e79-4d5a-807b-bc8ebec22654",
        "name": "Dr. Hibbert",
        "isZocd": true
    },
    {
        "id": "a9002b2e-3bcd-4674-be20-fa801c308bef",
        "name": "Dr. Nick",
        "isZocd": false
    }
]

.NET MVC allows us to overload the Docs action by filtering on the HTTP verb. This is great, however each overloaded method must contain different arguments. To get around this, we’ll create three distinct methods to handle POST, PUT and DELETE requests and alias the action name back to “Docs” (hat tip).

[ActionName("Docs")]
[HttpPost]
public ActionResult HandlePostDoc(Doctor doc)
{
}

[ActionName("Docs")]
[HttpPut]
public ActionResult HandlePutDoc(Doctor doc)
{
}

[ActionName("Docs")]
[HttpDelete]
public ActionResult HandleDeleteDoc(Guid id)
{
}

Let’s take a closer look at the arguments accepted by these methods. The HandlePostDoc and HandlePutDoc methods both accept the strongly-typed Doctor model we defined earlier. Our Backbone application will send the JSON serialized model in the request body, and .NET will automatically bind it to the specified type. Pretty cool! The completed service looks something like this:

public class ZocController : Controller
{
    public ActionResult Docs()
    {
        return Json(GetDocs(), JsonRequestBehavior.AllowGet);
    }

    [ActionName("Docs")]
    [HttpPost]
    public ActionResult HandlePostDoc(Doctor doc)
    {
        doc.id = Guid.NewGuid();

        CreateDoc(doc);

        return Json(doc);
    }

    [ActionName("Docs")]
    [HttpPut]
    public ActionResult HandlePutDoc(Doctor doc)
    {
        UpdateDoc(doc);

        return new EmptyResult();
    }

    [ActionName("Docs")]
    [HttpDelete]
    public ActionResult HandleDeleteDoc(Guid id)
    {
        DeleteDoc(id);

        return new EmptyResult();
    }
}

NOTE: Backbone uses the id of a model to check for server persistence. In our HandlePostDoc method, we set the id and return the serialized model to let Backbone know that the state has been saved on the server.

Binding Backbone to the Server

Now we’ll define our Doctor on the client using Backbone’s Model and Collection classes. Because Backbone assumes a conventional REST API, the Collection’s url attribute is all that’s needed to sync the client and server. (If you’re not using a RESTful service, it’s pretty straightforward to override Backbone’s sync method with one of your own.)

window.Doctor = Backbone.Model;

window.Doctors = Backbone.Collection.extend({

    model: Doctor,

    url: '/zoc/docs'

});

Manipulating Data on the Client

If everything is implemented correctly, our application will perform full CRUD operations asynchronously on the client and persist state to the server. For example, we can request a list of doctors:

var doctors = new Doctors();

doctors.fetch();

GET /zoc/docs

Create a new doctor:

doctors.create({
    name: 'Dr. Zoidberg',
    isZocd: false
});

POST /zoc/docs

Get an existing doc and modify some properties:

var doctor = doctors.get('4a93fa1e-aa91-4c9f-966a-035f3575d563');

doctor.save({
    isZocd: true
});

PUT /zoc/docs/4a93fa1e-aa91-4c9f-966a-035f3575d563

And delete a doctor completely:

doctor.destroy();

DELETE /zoc/docs/4a93fa1e-aa91-4c9f-966a-035f3575d563