Backbone.js Compatible Routes for, non-Web API, ASP.NET MVC projects

Backbone.js has become my JavaScript MV* framework of choice (you do use a JavaScript MV* framework, to structure JavaScript, right?). I find myself using ASP.NET MVC for less and less that has something to do with views, and other stuff that belongs on the client.

The server, being ASP.NET MVC, is the new backend now a days. I’m sure that’s how Microsoft sees it as well, with the release of Web API.

When using models in Backbone, and specifying the urlRoot setting, Backbone will automatically construct URLs to do CRUD operations against your server. It uses RESTful URLs, and regular ASP.NET MVC controllers are not compatible with those out of the box.

Consider this Backbone model and collection:

var Account = Backbone.Model.extend({ urlRoot: "/accounts" }); var AccountCollection = Backbone.Collection.extend({ model: Account, url: "/accounts" });

When you save a new Account, Backbone will make a POST request to your server expecting to hit your AccountsController. But with regular MVC controllers (non-Web API, that is) the routing won’t succeed, since the default {controller}/Index action only works with the default, parameter less GET request.

You might think that this controller will work:

public class AccountsController : Controller { [HttpGet] public JsonResult Index() { List accounts = new List(); accounts.Add(new Account { Id = 1, Name = "Account 1" }); accounts.Add(new Account { Id = 2, Name = "Account 2" }); accounts.Add(new Account { Id = 3, Name = "Account 3" }); return Json(accounts, JsonRequestBehavior.AllowGet); } [HttpGet] public JsonResult Index(int id) { return Json(new Account { Id = id, Name = "Account " + id }, JsonRequestBehavior.AllowGet); } [HttpPost] public JsonResult Index(Account model) { return Json(model); } [HttpPut] public JsonResult Index(int id, Account model) { return Json(model); } [HttpDelete] public ActionResult Index(int id) { return View(); } }

But as mentioned before, the default Index routes doesn’t work that way. This won’t even build, since the method signature of Get and Delete are the same.

Route constraints to the rescure

Ideally we want our controller to look like the following:

public class AccountsController : Controller { [HttpGet] public JsonResult GetAll() { List accounts = new List(); accounts.Add(new Account { Id = 1, Name = "Account 1" }); accounts.Add(new Account { Id = 2, Name = "Account 2" }); accounts.Add(new Account { Id = 3, Name = "Account 3" }); return Json(accounts, JsonRequestBehavior.AllowGet); } [HttpGet] public JsonResult Get(int id) { return Json(new Account { Id = id, Name = "Account " + id }, JsonRequestBehavior.AllowGet); } [HttpPost] public JsonResult Create(Account model) { return Json(model); } [HttpPut] public JsonResult Update(int id, Account model) { return Json(model); } [HttpDelete] public ActionResult Delete(int id) { return View(); } }

So for this to work, we need to point the individual HTTP verbs to their own action. This can be done by adding an HTTP method route constraints, when mapping routes in your RouteConfig (or Global.asax):

routes.MapRoute("Model_GetAll", "{controller}", new { action = "GetAll" }, new { httpMethod = new HttpMethodConstraint("GET") }); routes.MapRoute("Model_GetOne", "{controller}/{id}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint("GET"), id = @"^\d+$" }); routes.MapRoute("Model_Post", "{controller}", new { action = "Create" }, new { httpMethod = new HttpMethodConstraint("POST") }); routes.MapRoute("Model_Put", "{controller}/{id}", new { action = "Update" }, new { httpMethod = new HttpMethodConstraint("PUT") }); routes.MapRoute("Model_Delete", "{controller}/{id}", new { action = "Delete" }, new { httpMethod = new HttpMethodConstraint("DELETE") });

Now you can code away in Backbone, and always hit the correct MVC controller actions. If you want to avoid this, upgrade your MVC app to MVC 4 and use Web API controllers. They support this out of the box.