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






4.66/5 (10投票s)
这是一个在 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)。
背景
- 我们将从数据库中获取流派列表作为集合,并在 Backbone 视图中展示。(HTTP GET)
- 点击更新时,我们会将描述更新为“unknown”并更新数据库。(HTTP PUT)
- 点击删除时,我们将从视图和数据库中删除该记录。(HTTP DELETE)
- 点击创建时,我们将添加一个类似 [{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