Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Simple CRUD Using Backbone.js in ASP.NET MVC Razor

4.66/5 (11 votes)
22 Apr 2012CPOL6 min read 81.6K   4K  
Here is a simple CRUD on the MVC music store using backbone.

Introduction

In this tutorial lets probe into the implementation of backbone.js on the mvc music store application (http://mvcmusicstore.codeplex.com/) created on mvc 3 razor.

Backbone.js (http://documentcloud.github.com/backbone/)

Backbone is a javascript plugin which provides a mvc framework to build client side application easily. Really!! well that's for you to assert. This is a claim from backbone.js.

Here is a tutorial available on youtube (http://www.youtube.com/watch?v=vJwgIth1I_w&feature=related) which pretty much explains the purpose of backbone.

In this tutorial we going to create a new view on the mvc music store to List, Add, Delete and Update Genres.

Background

  1. We are going to List the Genres from the database as collection the backbone view. (HTTP GET)
  2. While clicking update we would update the description to unknown and update the database. (HTTP PUT)
  3. While clicking the delete we would delete the record from the view as well as from the database. (HTTP DELETE)
  4. While clicking the create new we would add an entry like [{name:"Unknown", Description:"Unknown" }]. (HTTP POST) 

Lets keep this as simple as possible. I would upload another tutorial in which we shall discuss the real time updates and inserts on the backbone.

Using the Code

Controller Methods

In the store manager controller lets add the following methods for the Genres view. The Genres Action is to return the StoreManager/Genres view which has our backbone script. Please note the return tupe of the GenreList Actions are all JSONResults and also note the AcceptVerbs. Backbone recognises the methods by name and the http action.

Http:Get StoreManager/GenreList/1 will call the get action for Genreid = 1 returns the selected genre.

Http:Get StoreManager/GenreList/ will call the get action returns all genres.

Http:put StoreManager/GenreList/1 will call the put action it will update the genre with Genreid =1

Http:Delete StoreManager/GenreList/1 will call the delete action to delete the genre with GenreId = 1

Http:Post StoreManager/GenreList/ will insert the new genre.

Things to note is that the action method names are all same 'GenreList'.

C#
// Genre Page View

[AcceptVerbs(HttpVerbs.Get)]

public ActionResult Genres(int? Id)

{

return View();

}

// Get Genres

[AcceptVerbs(HttpVerbs.Get)]

public JsonResult GenreList (int? Id)

{

if (Id.HasValue)

{

var genre = db.Genres

.Where(x => x.GenreId == Id.Value)

.Select(x => new { id = x.GenreId, GenreId = x.GenreId, Name = x.Name, Description = x.Description })

.FirstOrDefault();

return Json(genre, JsonRequestBehavior.AllowGet);

}

var genres = db.Genres

.Select(x => new { id = x.GenreId, GenreId = x.GenreId, Name = x.Name, Description = x.Description })

.ToList();

var result = Json(genres, JsonRequestBehavior.AllowGet);

return result;

}

// Update Genre

[AcceptVerbs(HttpVerbs.Put)]

public JsonResult GenreList(int Id, Genre UpdatedGenre)

{

var genre = db.Genres.Where(x => x.GenreId == Id).FirstOrDefault();

genre.Name = UpdatedGenre.Name;

genre.Description = UpdatedGenre.Description;

db.SaveChanges();

return Json(genre, JsonRequestBehavior.DenyGet);

}

//Add Genre

[AcceptVerbs(HttpVerbs.Post)]

public JsonResult GenreList(Genre CreateGenre)

{

if (CreateGenre.GenreId != 0)

{

return GenreList(CreateGenre.GenreId, CreateGenre);

}

else

{

db.Genres.Add(CreateGenre);

db.SaveChanges();

} 

return Json(CreateGenre, JsonRequestBehavior.DenyGet);

}

//Delete Genre

[AcceptVerbs(HttpVerbs.Delete)]

public JsonResult GenreList(int Id)

{

var genre = db.Genres.Where(x => x.GenreId == Id).FirstOrDefault();

db.Genres.Remove(genre);

db.SaveChanges();

return Json(genre, JsonRequestBehavior.DenyGet);

}

Genre View

'#Genre_Container' is going to be our actual view in which we would be populating the Genre collection. We have this '#Genre_List' in which we have defined the header of the table 'Name' and 'Description'. We would be using the '#Genre-Template' to clone and populate table with the genre list. Note that the create new button and the edit, delete buttons are enclosed with in the '#Genre_Container'. Backbone will only recognise the events of the DOM within its application view.

ASP.NET
<script src="@Url.Content("~/Scripts/StoreManager/Genre.js")" type="text/javascript"></script>

<div class="styler">

<fieldset class="ui-widget">

<legend class="ui-state-legend-default ui-corner-top ui-corner-bottom">Genre List -

Using Backbone</legend>

<div id="Genre_Container">

<input type="button" value= "Create New" class="Add" id="btnCreateNew" />

<table id="Genre_List">

<tr>

<th>

Name

</th>

<th>

Description

</th>

<th>



</th>

</tr>

</table>

</div>

<script id='Genre-Template' type='text/template'>

<td><%=Name%></td> <td><%=Description%></td>

<td><input type="button" value= "Edit" class="Edit" /> | <input type="button" value= "Details" class="Detail" /> | <input type="button" value= "Delete" class="Delete" /> </td>

</script>

</fieldset>

</div>

Genre.Js

 We would first create a Genre model inherited from the Backbone.Model. 

Genre Model

  • URL - This property contains the url which backbone can use to ajax get a Genre data. In this case it is pointing to /StoreManager/GenreList/id.
  • Initialize - Constructor of the Genre model.
  • defaults - Set the default values for the Genre model properties. At the time of instantiating an object if the object properties are null, it would fill it with the default values.

Since our Genres page lists a collection of genres, we will be creating a Genre Collection inherited from Backbone.Collection. If a page you are dealing with displays a single Object information say Genre you may not need to use the collections. It all depends on the requirement.

Genre Collection

  • model - This property sets the type of the Collection. In our case its set to Genre type.
  • url - This property contains the url which backbone can use to Ajax get the Genres data and load the collection. The collection is loaded using the fetch function. In our case its pointing to /StoreManager/GenreList/.

Now we have the models created, lets create some views to display the data. We will create a Genre View which for each Genre object in the collection. Also a AppView to display to append the genreview to the  '#Genre_List'.

JavaScript
$(function () {

// Genre Model

var Genre = Backbone.Model.extend({

url: function () {

var base = '/StoreManager/GenreList/';

if (this.isNew())

return base;

return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + this.id;

},

initialize: function () {

console.log('Genre Constructor Triggered');

},

defaults: {

GenreId: 0,

Name: 'Unknown',

Description: 'Unknown'

}

});

// Genre Collection

var GenreCollection = Backbone.Collection.extend({

model: Genre,

url: '/StoreManager/GenreList/'

});

// Genre View - el returns the template enclosed within a tr

var GenreView = Backbone.View.extend({

template: _.template($('#Genre-Template').html()),

tagName: "tr",

initialize: function () {

console.log('GenreView Constructor Triggered');

this.model.bind('change', this.render, this);

this.model.bind('remove', this.unrender, this);

},

render: function () {

console.log('Rendering...');

$(this.el).html(this.template(this.model.toJSON()));

return this;

},

unrender: function () {

console.log('Un-Rendering...');

$(this.el).remove();

return this;

},

events: {

"click .Edit": 'EditGenre',

"click .Delete": 'DeleteGenre'

},

EditGenre: function () {

this.model.set({ Description: 'Unknown' });

var self = this;

this.model.save(this.model, { success: function () {

$("input:button", $(self.el)).button();

}

});

},

DeleteGenre: function () {

this.model.destroy();

}

});

// Actual App view

var AppView = Backbone.View.extend({

initialize: function () {

this.collection.bind('add', this.AppendGenre, this);

},

el: '#Genre_Container',

counter: 15,

events: {

"click #btnCreateNew": "AddNewGenre"

},

AddNewGenre: function () {

console.log('Add Genre....');

this.counter++;

var newGenre = new Genre({ Name: 'Unknown ' + this.counter, Description: 'Damn ' + this.counter });

this.collection.add(newGenre);

newGenre.save(newGenre, { success: function () {

$("input:button", "#Genre_List").button();

}

});

},

AppendGenre: function (genre) {

var genreView = new GenreView({ model: genre });

$(this.el).find('table').append(genreView.render().el);

},

render: function () {

if (this.collection.length > 0) {

this.collection.each(this.AppendGenre, this);

}

$("input:button", "#Genre_List").button();

}

});

var genres = new GenreCollection();

var view = new AppView({ collection: genres });

genres.fetch({ success: function () {

view.render();

}

});

});

Genre View

  • template - This property points to the template script in the html. We will use _.template function to substitute the object data inside the template. It our case it returns the html <td>name</td><td>description</td>
  • tag - This property has the DOM tag to be used to enclose the genre view. In our case its a <tr>. So the Genre View would return a html like <tr>template</tr>. Failing to give the tag property will force backbone to assume that the tag is <div>.
  • model - The model property will hold the Genre object.
  • Initialize - Its the constructor. Inside the constructor we will bind any change event on the model to render event of the genre view and remove event to unrender event.
  • render - the render function parses the template against the model object and returns the genre view in our case the table row.
  • unrender - the unrender function removes the corresponding genre view from the html.
  • Events - Any DOM element enclosed within the genre view can be event handled inside the view. We are handling the Edit, Delete button events in this case. Note that we need to use the class name of the buttons to trigger the events.
  • EditGenre - Triggered when Edit button is clicked. It updates the model.description to 'unknown' and saves the model. Saving the model will Ajax put StoreManager/GenreList/Id controller method. Since the model is changed, it would trigger the render method and that would update the DOM.
  • DeleteGenre - Triggered when Delete button is clicked. It will delete the model (destroy). It would Ajax delete StoreManager/GenreList/Id controller method. Since model is deleted as per the code in the constructor it wil trigger the unrender method. 

App View

Since we are dealing with Genre List we will be using the Collection property on the App View rather than the model property.

  • Initialize - In the constructor we bind the add event on the collection to AppendGenre event.
  • el - It is the DOM on which the Application view will be applied. In our case it is the '#Genre_Container'.
  • events - We bind the Add Genre button to the AddNewGenre event.
  • AppendGenre - It is called for each object in the collection. It instantiates a GenreView with model = genre and appends the genre view (after rendering) on the el. 
  • AddNewGenre - In this event we instantiate a new genre model object, add that to the collection. Then call the save on the genre object. Which will call the controller method and render the genre view. Add on the collection will trigger AppendGenre.
  • render - loops through the genre collection and appends the genre to the DOM. 

Now we are all set. The only this remaining is to instantiate a Genre Collection. Then Instantiate a Genre view and set its collection to genrecollection. The next step is to call fetch on the collection. On fetch Success we will render the App View.

Hope that explains the Backbone concepts. I' m working on enhancing the application to do real time updates and inserts, using dialog boxes and some validations. Keep watching this space for more updates. Also I would do a tutorial on Routes in backbone.

Points of Interest

After creating a new genre in the view, clicking the corresponding Edit triggers a POST instead of a PUT. I' m still reasearching into this. Right now the Post controller is written to handle both inserts and updates. Note that there is a weird javascript bug in the uploaded code which prevents it from working properly in internet explorer. The application works fine in Chrome, Firefox and Safari.

- ANON 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)