Backbone.js has quickly become one of JavaScript’s premier MV* frameworks. It offers simple flexibility, and language features that just make sense.
ASP.net MVC has also become one of the web’s most popular server side frameworks and the latest iteration, MVC4 was recently released into beta. One of it’s newer features is called, Web API and is a way of creating a REST service inside of an MVC application.
Along with the release of MVC4 is a new feature in Entity Framework Code First called Migrations. This offers a way of keeping track of all the changes to the data model over time.
And yet another popular web feature is CoffeeScript. Written by the same guy that created Backbone, and Underscore, CoffeeScript adds Ruby-esque language features to JavaScript, and also “transpiles” down into JavaScript. It can be compiled in a number of different ways including a coffee.exe, inside of Node.js, or directly in Visual Studio with Mindscape Web Workbench.
This post combines all of these exciting new things into one app for managing contacts.
First off, download and install the ASP.net MVC4 Beta.
Then go ahead and either get Mindscape, or get Node.js, and run
in a command prompt or terminal window.
Next, open up Visual Studio and File –> New Project.
There should be a new option for ASP.NET MVC 4 Web Application. Hit ok, and then choose Empty from the next window.
This will give you a brand spankin’ new MVC4 application. Let’s begin with creating the data model.
Please note: All the code below is online at…
https://github.com/jcreamer898/BackboneCoffeeWebApi
The code below is broken down into Entity Framework, Web API, and CoffeeScript/Backbone.
Entity Framework
First off we’ll create a Customer class, and a DbContext.
public class Customer { public int Id { get; set; } [Required] public string FirstName { get; set; } [Required] public string LastName { get; set; } [Required] [Email] public string Email { get; set; } public string Phone { get; set; } public string Description { get; set; } } public class ManagerContext : DbContext { public DbSet Customers { get; set; } }
The extra DataAnnotation of Email is from the NuGet package DataAnnotationsExtensions.MVC3.
Next is upgrading Entity Framework and Enabling migrations. This is accomplished by the following three commands in the Package Manager Console…
>Update-Package EntityFramework >Enable-Migrations >Add-Migration FirstDatabaseCreate >Update-Database
A new folder called Migrations will be added to your solution and in it will be to files, Configuration, and TIMESTAMP_FirstDatabaseCreate.cs. The FirstDatabaseCreate.cs provides an up and down for moving through different versions of your database. Update-Database will go ahead and create the database for you.
The configuration file also has a seed method for seeding the database…
internal sealed class Configuration : DbMigrationsConfiguration { public Configuration() { AutomaticMigrationsEnabled = false; } protected override void Seed(CustomerManager.Models.ManagerContext context) { // This method will be called after migrating to the latest version. // You can use the DbSet.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. E.g. // // context.People.AddOrUpdate( // p => p.FullName, // new Person { FullName = "Andrew Peters" }, // new Person { FullName = "Brice Lambson" }, // new Person { FullName = "Rowan Miller" } // ); // context.Customers.Add(new Customer { FirstName = "Joe", LastName = "Schmoe", Description = "Awesome", Email = "joe@schmoe.com", Phone = "6155551234" }); } }
That’s it for the data model, now onto the Web API stuff. Web API is still currently in Beta, but it is in pretty darn good shape already as Scott Gu lists some of its awesome features. Most notable are Automatic Content Negotiation, built in IoC support, and a Modern HTTP model.
Web API
You’ll notice in a new MVC4 application, in the global.asax a new route will be added…
routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );
This allows you to begin adding Api controllers which inherit from a new class called ApiController. Go ahead and right click on Controllers and create a new controller. In the drop down box choose API Controller.
This will generate a new controller that looks something like…
public class CustomersController : ApiController { // GET /api/customers public IEnumerable Get() { return new string[] { "value1", "value2" }; } // GET /api/customers/5 public string Get(int id) { return "value"; } // POST /api/customers public void Post(string value) { } // PUT /api/customers/5 public void Put(int id, string value) { } // DELETE /api/customers/5 public void Delete(int id) { } }
By convention the name of the Http method becomes the name of the Action in the Controller. Fast forward a little bit, adding in all of the retrieval methods for the the methods, and…
public class CustomersController : ApiController { private ManagerContext _managerContext; public CustomersController() { _managerContext = new ManagerContext(); } // GET /api/customers public IEnumerable Get() { return _managerContext.Customers; } // GET /api/customers/5 public Customer Get(int id) { return _managerContext.Customers.SingleOrDefault(c => c.Id == id); } // POST /api/customers public HttpResponseMessage Post(Customer customer) { JsonArray errors = ParseErrors(); if (errors.Count > 0) { return new HttpResponseMessage(errors, HttpStatusCode.Forbidden); } _managerContext.Customers.Add(customer); _managerContext.SaveChanges(); return new HttpResponseMessage(customer); } // PUT /api/customers/5 public HttpResponseMessage Put(int id, Customer customer) { JsonArray errors = ParseErrors(); if(errors.Count > 0) { return new HttpResponseMessage(errors, HttpStatusCode.Forbidden); } _managerContext.Entry(customer).State = EntityState.Modified; _managerContext.SaveChanges(); return new HttpResponseMessage(customer); } // DELETE /api/customers/5 public void Delete(int id) { var customer = _managerContext.Customers.SingleOrDefault(c => c.Id == id); _managerContext.Customers.Remove(customer); _managerContext.SaveChanges(); } private JsonArray ParseErrors() { var errors = new JsonArray(); // Validate movie if (!ModelState.IsValid) { foreach (var prop in ModelState.Values) { if (prop.Errors.Any()) { errors.Add(prop.Errors.First().ErrorMessage); } } } return errors; } }
Whew, a lot going on there, but not too much. It’s pretty much routine in terms of retrieving things from a DbContext in EF. The newer-ish things are in the Put and Post you can see the return type is HttpResponseMessage. This class is used to wrap a response and return specific error codes. The other is a private method called ParseErrors. This method loops over all of the Values in the ModelState and determines if there are any errors. If so, it will gather them into an array and return them with a 403 error to the client.
That takes care of the Web API stuff for now. You can test that the API is working by firing up a debugger and hitting /Api/Customers. For further info on the Web API, checkout Jon Galloway’s series of 6 posts.
Next, create a HomeController, layout, and an index view just like in MVC3. Then go ahead and either add Backbone, Underscore, and Handlebars to the application. Handlebars.js is a very good templating framework for JavaScript.
CoffeeScript/Backbone
Enter CoffeeScript. Using the Mindscape Web Workbench extension, you can add CoffeeScript files to your application.
Every time you write CoffeeScript and save it, it will automatically, “transpile” your CoffeeScript into JavaScript. Then you can reference app.js in the layout.
Now begin the Backbone CoffeeScript fun! I’ll start with the models.
class Customer extends Backbone.Model urlRoot: '/api/customers/' idAttribute: 'Id' validate: (attr) => if !attr.FirstName return "First Name is required" if !attr.LastName return "Last Name is required" if !attr.Email return "Email Address is required" class Customers extends Backbone.Collection url: '/api/customers/' model: Customer
This code creates a Customer Model as well as a Customer Collection. The Customer model has some validation that will come into play whenever the Customer’s attributes are set, or the model is saved. The urlRoot is the main Url from our Web API. Backbone.Sync will use GET on model.fetch POST on model.save when the Id of the model hasn’t been set, PUT on model.save when the Id IS set, and DELETE on model.destroy.
Next we’ll create some Customer Views.
class CustomerList extends Backbone.View initialize: => @collection.on 'reset', @renderAll @collection.on 'add', @render @collection.fetch() _.bindAll @,'renderAll' return this renderAll: () => @collection.each @render return this render: (model) => item = new CustomerItem model: model @$el.append item.el return this class CustomerItem extends Backbone.View tagName: 'tr' initialize: => @template = Handlebars.compile($('#customer').html()) @model.on('change', @render) @render() return this events: 'click .remove': 'deleteItem', 'click .edit': 'showEdit' render: => html = @template @model.toJSON() @$el.html('').append(html) return this showEdit: (event) => event.preventDefault() Vent.trigger 'edit', @model return this deleteItem: => @model.destroy() @$.fadeOut 'fast' @remove() messages.success 'Deleted!' return this class CustomerEdit extends Backbone.View el: '#customerEdit' events: 'click #save': 'save' 'click #cancel': 'cancel' initialize: => Vent.on 'edit', @render @template = Handlebars.compile $('#customerEditTemplate').html() render: (model) => if model @model = model else @model = new Customer() data = @model.toJSON() html = @template data @$el.html(html).show() .find('#first').focus() @model.on 'error', @showError return this; save: (event) => @model.save 'FirstName': @$el.find('#first').val() 'LastName': @$el.find('#last').val() 'Email': @$el.find('#email').val() 'Phone': @$el.find('#phone').val() 'Birthday': @$el.find('#birthday').val() 'Description': @$el.find('#description').val() , wait: true success: => messages.success 'Saved!' window.customers.add @model unless window.customers.any( (customer) => return customer.get('Id') is @model.get('Id'); ) @$el.hide() return this cancel: => @$el.hide() showError: (model, error) => error = JSON.parse(error.responseText).join '<br \ />' if (typeof error is 'object') messages.error error
The CustomerList is responsible for managing the entire list of customers. And anytime the collection is retrieved from the server or added to, it will create a new CustomerItem view and append it to the CustomerList. The CustomerItem uses a customer template found in the following HTML code, and is compiled with Handlebars.js.
The CustomerEdit view is used to edit, no surprise there! The initialize method stores a compiled Handlebars template for editing a customer. The save method grabs all of the values off of the inputs, and then calls model.save(). This of course causes the server to either PUT or POST depending on the result of model.isNew().
<script id="customer" type="text/html"> <td>{{ FirstName }}</td> <td>{{ LastName }}</td> <td>{{ Email }}</td> <td colspan="2"> <a href="#edit" class="edit" data-id="{{Id}}"><i class="icon-pencil"></i></a> <a href="#remove" class="remove" data-id="{{Id}}"><i class="icon-remove"></i></a> </td> </script> <script id="customerEditTemplate" type="text/html"> <input type="hidden" name="id" id="id" value="{{Id}}"/> <input type="text" name="first" id="first" value="{{FirstName}}" placeholder="First Name"/> <input type="text" name="last" id="last" value="{{LastName}}" placeholder="Last Name"/> <input type="email" name="email" id="email" value="{{Email}}" placeholder="Email"/> <input type="text" name="phone" id="phone" value="{{Phone}}" placeholder="Phone"/> <textarea name="description" id="description">{{Description}}</textarea> <a class="btn btn-primary btn-large" id="save">Save</a> <a class="btn btn-primary btn-large" id="cancel">Cancel</a> </script>
One of the interesting things in the CustomerItem view is the concept of a Vent. I saw Derick Bailey use this technique. It combines a few patterns, one called Pub/Sub or Observer, and another called the Mediator pattern.
The Observer pattern basically allows objects to publish or subscribe to events. Much the same way that when working with browsers, when a user clicks in the browser, the browser publishes a click event, and some JavaScript or jQuery code listens to the event.
The Mediator pattern assigns an object the responsibility of handling all pub/sub requests.
The Observer pattern is implemented through Backbone.Events, and can be used with the Mediator pattern by…
class Vent extends Backbone.Events window.Vent = Vent
Now you have a global Vent object that is responsible for managing pub/sub events. In the CustomerItem view above you can see the line of code, Vent.trigger ‘edit’, @model
In CustomerEdit there is some code that listens to the edit event Vent.on ‘edit’, @render and fires the render method when it is triggered.
Also in CustomerEdit I used a view called MessageManager in order to display messages to the users.
class MessageManager extends Backbone.View el: '.alert' render: (type, message, opts) => defaults = fade: 3000 _.extend defaults, opts typeClass = "alert alert-#{type}"; @$el.empty().prepend(message).removeClass().addClass(typeClass).fadeIn 'fast' setTimeout (=> @$el.fadeOut()), defaults.fade error: (message, opts) => @render 'error', message, opts unless !message success: (message, opts) => @render 'success', message, opts unless !message
Pretty basic stuff here, just a few methods for triggering different types of messages.
The last piece needed in the JavaScript code is the initialization of the different views.
$ -> window.customers = new Customers edit = new CustomerEdit() list = new CustomerList collection: customers el: '#customerList' $('#add').click -> Vent.trigger 'edit' $(".alert").alert() window.messages = new MessageManager()
This code here wraps the initialization in a jQuery ready, and then starts newing stuff up. An instance of the Customers collection, and an instance of the messages view are stored in the window so that they can be accessed anywhere in the app, and the edit and list views are also initialized.
Last is the simple attaching of a click handler to my add button which triggers the Edit event and allows for adding a new customer.
Again all of the above code is available at
https://github.com/jcreamer898/BackboneCoffeeWebApi/
And in the /Scripts/ directory there is an app.coffee, and it’s code behind app.js, as well as an actual representation of the JS at app.javascript.js
Hope you enjoy!