Integrating Backbone.js with ASP.NET Web API
In case you did not see the latest news, what we used to know as WCF Web API was recently rebranded and included in ASP.NET MVC 4 as ASP.NET Web API. While both frameworks are similar in essence with focus on HTTP, the latter was primarily designed for building HTTP services that don’t typically require an user intervention. For example, some AJAX endpoints or a Web API for a mobile application. While you could use ASP.NET MVC for implementing those kind of services, that would require some extra work for implementing things right like content-negotiation, documentation, versioning, etc. What really matter is that both framework share many of the extensibility points like model binders, filters or routing to name a few.
If you don’t know what Backbone.js is, this quote from the main page in the framework website will give you some idea,
“Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface. “
You see, the framework will help you to organize your javascript code for the client side using an MVC pattern, and it will also provide an infrastructure for connecting your models with an existing Web API. (I wouldn’t use the term RESTful here as it usually implies your services adhere to all the tenets discussed in the Roy Fielding’s dissertation about REST).
That means you should be able to connect the backbone.js models with your MVC Web API in a more natural way. As part of this post, I will use the “Todos” example included as part of the backbone.js code, but I will connect that to a Web API built using the new ASP.NET Web API framework.
On the client side, a model is defined for representing a Todo item.
// Our basic **Todo** model has `id`, `text`, `order`, and `done` attributes.
window.Todo = Backbone.Model.extend({
idAttribute: 'Id',
// The rest of model definition goes here
});
There is also a collection for representing the Todo list,
// The collection of todos is backed by *localStorage* instead of a remote
// server.
window.TodoList = Backbone.Collection.extend({
// Reference to this collection's model.
model: Todo,
url: function () {
return 'api/todos';
},
// The rest of the collection definition goes here
});
I modified the code for this collection to override the “url” setting. This new url will point to the route of our Web API controller. Backbone will try to synchronize all the changes in the models by sending http requests with the corresponding verb (GET, POST, PUT or DELETE) to that url.
Now, the interesting part is how we can define the Web API controller using the new ASP.NET Web Api framework.
public class TodosController : ApiController
{
public IEnumerable<TodoItem> Get()
{
return TodoRepository.Items;
}
public IdModel Post(TodoItem item)
{
item.Id = Guid.NewGuid().ToString();
TodoRepository.Items.Add(item);
return new IdModel{ Id = item.Id };
}
public HttpResponseMessage Put(string id, TodoItem item)
{
var existingItem = TodoRepository.Items.FirstOrDefault(i => i.Id == id);
if (existingItem == null)
{
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
existingItem.Text = item.Text;
existingItem.Done = item.Done;
return new HttpResponseMessage(HttpStatusCode.OK);
}
public void Delete(string id)
{
TodoRepository.Items.RemoveAll(i => i.Id == id);
}
}
As you can see, the code for implementing the controller is very clean and neat. A name convention based on the Http Verbs can be used to route the requests to the right controller method. Therefore, I have different methods for retrieving the list of items (GET), creating a new item (POST), updating an existing item (PUT) or just delete it (DELETE)
A few things to comment about the code above,
- I haven’t specified the wire format anywhere in the code. The framework will be smart enough to do content negotiation based on the headers sent by the client side (In this case text/json).
- You can return any serializable class or a HttpResponseMessage if you want to have better control over the returned message (set the status code for example).
- The framework will take care of serialize or deserialize the message on the wire to the right model (TodoItem in this case)
- A hardcoded repository is being used, which is not a good practice at all, but you can easily inject any dependency into the controllers as you would do with the traditional ASP.NET MVC controllers.
Here is the class definition for the TodoItem model,
public class TodoItem : IdModel
{
public int Order { get; set; }
public string Text { get; set; }
public bool Done { get; set; }
}
The IdModel is just a workaround for returning only an Id property to the client side when a new item is created. This is what backbone needs to associate an id to a new model. A more elegant solution would be to return an anonymous object with the id (such as new { Id = “xxx” }), but the default serializer for Json in the current bits (DataContractJsonSerializer) can not serialize them. Extending the built-in formatter to use a different serializer is something I am going to show in a different post.
public class IdModel
{
public string Id { get; set; }
}
The default routing rule in the global.asax file is configured like this,
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
All the calls to a API controller must be prefixed with “api”. That’s why I set the url to “api/todos” in the backbone model.