65.9K
CodeProject 正在变化。 阅读更多。
Home

在 ASP.NET MVC Razor 中使用 Backbone.js 实现简单的 CRUD

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.66/5 (10投票s)

2012年4月22日

CPOL

6分钟阅读

viewsIcon

82196

downloadIcon

4038

这是一个在 MVC 音乐商店项目中使用 Backbone 实现简单 CRUD 操作的示例。

引言

在本教程中,我们将探讨如何在基于 MVC 3 Razor 创建的 MVC 音乐商店应用程序(http://mvcmusicstore.codeplex.com/)中实现 backbone.js。

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

Backbone 是一个 JavaScript 插件,它提供了一个 MVC 框架,可以轻松构建客户端应用程序。真的吗?这得由您来判断。这是 backbone.js 的自述。

YouTube 上有一个教程(http://www.youtube.com/watch?v=vJwgIth1I_w&feature=related),详细解释了 Backbone 的用途。

在本教程中,我们将在 MVC 音乐商店中创建一个新视图,用于列出、添加、删除和更新音乐流派(Genres)。

背景

  1. 我们将从数据库中获取流派列表作为集合,并在 Backbone 视图中展示。(HTTP GET)
  2. 点击更新时,我们会将描述更新为“unknown”并更新数据库。(HTTP PUT)
  3. 点击删除时,我们将从视图和数据库中删除该记录。(HTTP DELETE)
  4. 点击创建时,我们将添加一个类似 [{name:"Unknown", Description:"Unknown" }] 的条目。(HTTP POST) 

让我们尽可能保持简单。我将上传另一个教程,讨论 Backbone 中的实时更新和插入。

Using the Code

控制器方法

在 StoreManager 控制器中,我们为 Genres 视图添加以下方法。Genres Action 用于返回包含我们 Backbone 脚本的 StoreManager/Genres 视图。请注意,GenreList Action 的返回类型都是 JSONResults,同时也要注意 AcceptVerbs。Backbone 通过名称和 HTTP 动作来识别这些方法。

Http:Get StoreManager/GenreList/1 将调用 Get 操作,获取 Genreid = 1 的记录,返回选定的流派。

Http:Get StoreManager/GenreList/ 将调用 Get 操作,返回所有流派。

Http:put StoreManager/GenreList/1 将调用 Put 操作,更新 Genreid = 1 的流派。

Http:Delete StoreManager/GenreList/1 将调用 Delete 操作,删除 GenreId = 1 的流派。

Http:Post StoreManager/GenreList/ 将插入新的流派。

需要注意的是,这些 Action 方法的名称都是相同的“GenreList”。

// 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 视图

“#Genre_Container” 将是我们的实际视图容器,我们将在其中填充 Genre 集合。我们有一个 “#Genre_List”,其中定义了表格的标题“Name”和“Description”。我们将使用 “#Genre-Template” 来克隆并填充流派列表。请注意,“创建”按钮以及“编辑”、“删除”按钮都包含在 “#Genre_Container” 中。Backbone 只会识别其应用程序视图内的 DOM 事件。

<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

 我们首先创建一个继承自 Backbone.Model 的 Genre 模型。 

Genre 模型

  • URL - 此属性包含 Backbone 用来通过 Ajax 获取单个 Genre 数据的 URL。在本例中,它指向 /StoreManager/GenreList/id。
  • Initialize - Genre 模型的构造函数。
  • defaults - 为 Genre 模型的属性设置默认值。在实例化一个对象时,如果对象的属性为 null,它将被填充为默认值。

由于我们的 Genres 页面列出的是流派的集合,因此我们将创建一个继承自 Backbone.Collection 的 Genre 集合。如果您处理的页面只显示单个对象信息(例如单个 Genre),则可能不需要使用集合。这完全取决于需求。

Genre 集合

  • model - 此属性设置集合的类型。在我们的例子中,它被设置为 Genre 类型。
  • url - 此属性包含 Backbone 用来通过 Ajax 获取 Genres 数据并加载集合的 URL。集合使用 fetch 函数加载。在我们的例子中,它指向 /StoreManager/GenreList/。

现在我们已经创建了模型,接下来创建一些视图来显示数据。我们将为集合中的每个 Genre 对象创建一个 GenreView。同时,创建一个 AppView,用于将 GenreView 附加到 “#Genre_List” 中。

$(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 视图

  • template - 此属性指向 HTML 中的模板脚本。我们将使用 _.template 函数将对象数据替换到模板中。在我们的例子中,它返回 HTML <td>name</td><td>description</td>。
  • tag - 此属性指定用于包裹 Genre 视图的 DOM 标签。在我们的例子中是 <tr>。因此,GenreView 将返回类似 <tr>template</tr> 的 HTML。如果未提供 tag 属性,Backbone 将默认使用 <div> 标签。
  • model - model 属性将持有 Genre 对象。
  • Initialize - 这是构造函数。在构造函数中,我们将模型上的任何 change 事件绑定到 Genre 视图的 render 事件,将 remove 事件绑定到 unrender 事件。
  • render - render 函数根据模型对象解析模板,并返回 Genre 视图,在我们的例子中是表格行。
  • unrender - unrender 函数从 HTML 中移除相应的 Genre 视图。
  • Events - 任何包含在 Genre 视图内的 DOM 元素都可以在该视图内进行事件处理。在本例中,我们处理了“编辑”和“删除”按钮的事件。请注意,我们需要使用按钮的类名来触发事件。
  • EditGenre - 当点击“编辑”按钮时触发。它将模型的 description 更新为“unknown”并保存模型。保存模型将通过 Ajax 调用 StoreManager/GenreList/Id 控制器的 Put 方法。由于模型已更改,它将触发 render 方法,从而更新 DOM。
  • DeleteGenre - 当点击“删除”按钮时触发。它将删除(destroy)模型。这将通过 Ajax 调用 StoreManager/GenreList/Id 控制器的 Delete 方法。根据构造函数中的代码,模型被删除后将触发 unrender 方法。 

App 视图

由于我们处理的是 Genre 列表,因此我们将在 App 视图上使用 Collection 属性,而不是 model 属性。

  • Initialize - 在构造函数中,我们将集合的 add 事件绑定到 AppendGenre 事件。
  • el - 这是应用 Application 视图的 DOM 元素。在我们的例子中,它是“#Genre_Container”。
  • events - 我们将“添加流派”按钮绑定到 AddNewGenre 事件。
  • AppendGenre - 该方法对集合中的每个对象调用一次。它实例化一个 GenreView,其 model = genre,然后将(渲染后的)Genre 视图附加到 el 上。 
  • AddNewGenre - 在此事件中,我们实例化一个新的 Genre 模型对象,并将其添加到集合中。然后对该 Genre 对象调用 save 方法,这将调用控制器方法并渲染 Genre 视图。集合上的 add 操作将触发 AppendGenre。
  • render - 循环遍历 Genre 集合,并将每个 Genre 附加到 DOM 上。 

现在一切准备就绪。唯一剩下的就是实例化一个 Genre 集合。然后实例化一个 Genre 视图,并将其 collection 设置为 genrecollection。下一步是对集合调用 fetch 方法。在 fetch 成功后,我们将渲染 App 视图。

希望这解释了 Backbone 的概念。我正在努力增强这个应用程序,以实现实时更新和插入,使用对话框和一些验证。请继续关注此处的更多更新。我还会做一个关于 Backbone 中 Routes 的教程。

关注点

在视图中创建新流派后,点击相应的“编辑”按钮会触发 POST 请求而不是 PUT 请求。我仍在研究这个问题。目前,Post 控制器被编写为同时处理插入和更新。请注意,上传的代码中存在一个奇怪的 JavaScript 错误,导致它在 Internet Explorer 中无法正常工作。该应用程序在 Chrome、Firefox 和 Safari 中运行良好。

- ANON 

© . All rights reserved.