使用 ASP.NET MVC/Web Api 快速入门 KnockoutJS
在 ASP.net MVC 页面中使用 knockout.js 和 Web API 数据服务
引言
Knockoutjs 已经受到关注很久了。它是一个客户端数据绑定 JavaScript 库,可以帮助我们创建干净的数据模型,并负责处理事件和数据绑定。knockoutjs 实时反映视图变化,并且只更新已更改的区域。
使用 knockoutjs,您可以利用当今流行的技术构建时尚且响应迅速的 Web 应用程序。
在此演示中,让我们创建一个名为 student-register 的 ASP.NET MVC Web 应用程序,并使用 knockoutjs 进行客户端数据绑定。
我们使用 ASP.NET Web API 服务与服务器端持久化数据存储进行通信。
使用代码
让我们从 ASP.NET MVC (5) 应用程序和 Web API 开始,并创建一个 LocalDB 数据库作为数据存储。
然后,为我们的数据库创建实体数据模型,并通过 Web API 服务公开数据。
最后,我们将从客户端消耗 Web 服务,并使用 knockoutjs 将数据绑定到我们的视图。
我们在这里使用了集成的 Bootstrap 样式来实现干净、简单的 UI。
项目设置
创建带有 Web API 的 ASP.NET MVC 应用程序。
在 .NET framework 4.5 下添加新的 ASP.NET Web 应用程序。在第二个页面上,勾选 Web API 以添加 Web API 服务的核心引用。
将 KnockoutJS 库添加到项目中。
使用 nuget 包管理器,将 knockoutJS 库添加到项目中。
创建应用程序数据库。
在 app_data 文件夹下创建数据库文件,并使用 Visual Studio 提供的工具创建一个名为 students 的表。请参考截图获取数据表字段。
添加实体数据模型
向解决方案添加一个名为“EntityDataModel”的新文件夹,并将一个名为 StudentModel.edmx 的ADO.NET Entity Data Model添加到该文件夹中。
选择现有的 Student 数据库并选择 students 表。用于实体数据模型。生成后 EF 模型应如下所示。
创建 Web API 控制器
右键单击 controllers 文件夹,然后单击 add --> Controller。
选择 Web API 2 Controller with read/write actions 模板,然后单击 add。
将此控制器命名为 StudentController.cs
现在我们已经为解决方案设置了项目。在接下来的步骤中,我们将进行实现以达到所需的功能。
实现
让我们在“Models”文件夹下添加一个名为“StudentRepository”的新类,以便我们更轻松地访问 EF 数据模型。
using KnockoutMVC.EntityDataModel;
using System.Collections.Generic;
using System.Linq;
namespace KnockoutMVC.Models
{
/// <summary>
/// Student data repository
/// </summary>
public class StudentRepository
{
private static StudentDatabaseEntities _studentDb;
private static StudentDatabaseEntities StudentDb
{
get { return _studentDb ?? (_studentDb = new StudentDatabaseEntities()); }
}
/// <summary>
/// Gets the students.
/// </summary>
/// <returns>IEnumerable Student List</returns>
public static IEnumerable<Student> GetStudents()
{
var query = from students in StudentDb.Students select students;
return query.ToList();
}
/// <summary>
/// Inserts the student to database.
/// </summary>
/// <param name="student">The student object to insert.</param>
public static void InsertStudent(Student student)
{
StudentDb.Students.Add(student);
StudentDb.SaveChanges();
}
/// <summary>
/// Deletes student from database.
/// </summary>
/// <param name="studentId">Student ID</param>
public static void DeleteStudent(int studentId)
{
var deleteItem = StudentDb.Students.FirstOrDefault(c => c.Id == studentId);
if (deleteItem != null)
{
StudentDb.Students.Remove(deleteItem);
StudentDb.SaveChanges();
}
}
}
}
接下来,让我们更新我们的 Web API 控制器,借助 StudentRepository 类在 students 表上执行基本的读取/添加/删除操作。
using KnockoutMVC.EntityDataModel; using KnockoutMVC.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; namespace KnockoutMVC.Controllers { /// <summary> /// Student Api controller /// </summary> public class StudentController : ApiController { // GET api/student public IEnumerable<Student> Get() { return StudentRepository.GetStudents(); } // GET api/student/5 public Student Get(int id) { return StudentRepository.GetStudents().FirstOrDefault(s=>s.Id == id); } // POST api/student public HttpResponseMessage Post(Student student) { StudentRepository.InsertStudent(student); var response = Request.CreateResponse(HttpStatusCode.Created, student); string url = Url.Link("DefaultApi", new {student.Id}); response.Headers.Location = new Uri(url); return response; } // DELETE api/student/5 public HttpResponseMessage Delete(int id) { StudentRepository.DeleteStudent(id); var response = Request.CreateResponse(HttpStatusCode.OK, id); return response; } } }
创建 MVC 视图
让我们创建两个部分视图来显示已注册的学生列表和注册新学生。
稍后,我们可以将这两个部分视图添加到 Index 视图中,该视图将显示学生列表并提供注册新学生的功能。
注册学生部分视图
添加一个名为 _RegisterStudent.cshtml 的新部分视图。 您可以在“Home”视图文件夹下添加此文件,因为它不与其他控制器共享。
_RegisterStudent.cshtml
<form role="form">
<div class="form-group">
<label for="inpFirstName">First Name</label>
<input id="inpFirstName" type="text" class="form-control" data-bind="value: FirstName" />
</div>
<div class="form-group">
<label for="inpLastName">Last Name</label>
<input id="inpLastName" type="text" class="form-control" data-bind="value: LastName" />
</div>
<div class="form-group">
<label for="inpAge">Age</label>
<input id="inpAge" type="text" class="form-control" data-bind="value: Age" />
</div>
<div class="form-group">
<label for="inpGender">Gender</label>
<select id="inpGender" class="form-control" data-bind="options: genders, value: Gender"></select>
</div>
<div class="form-group">
<label for="txtDescription">Description</label>
<input id="txtDescription" class="form-control" data-bind="value: Description"/>
</div>
</form>
<input type="button" id="btnAddStudent" class="btn btn-primary" value="Add Student" data-bind="click: addStudent" />
我们在这里使用 Bootstrap 样式来美化表单元素(role="form", class="form-group", class="form-control", class="btn btn-primary")。
我们还添加了 knockout 数据绑定属性(data-bind="”)。此绑定将 knockout 视图模型与 HTML 元素属性关联,该属性在绑定表达式中指定。
例如,[data-bind="value: FirstName"] 表示将视图模型中的“FirstName”属性绑定到该元素的 value 属性。
如果您查看按钮的绑定表达式,它看起来像 [data-bind="click: addStudent"]。这表示将按钮的 click 事件绑定到视图模型的 addStudent 函数。
在创建我们的视图模型 JavaScript 文件时,我们将指定视图模型。
列表部分视图
让我们创建我们的列表部分视图。
添加另一个名为 _StudentsList.cshtml 的部分视图,其中包含以下标记。
_StudentsList.cshtml
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Age</th>
<th>Gender</th>
<th>Description</th>
<th>Action</th>
</tr>
</thead>
<tbody data-bind="foreach: students">
<tr>
<td data-bind="text: Id"></td>
<td data-bind="text: FullName"></td>
<td data-bind="text: Age"></td>
<td data-bind="text: Gender"></td>
<td data-bind="text: Description"></td>
<td><input type="button" class="btn btn-danger btn-xs" value=" [x] delete " data-bind="click: $parent.removeStudent" /></td>
</tr>
</tbody>
</table>
<br />
<input type="button" class="btn btn-default" id="btnGetStudents" value="Refresh" data-bind="click: getStudents" />
在 tbody 标签中,我们使用 [data-bind="foreach: students"] 绑定表达式。顾名思义,这是一个迭代表达式。
我们的视图模型中有一个名为 students 的学生对象集合,该表达式将为每个学生对象创建表行。
此外,按钮的绑定具有 [ data-bind="click: $parent.removeStudent"] 表达式。这表明 removeStudent 函数位于视图模型上下文之外的父级。当您将此与实际视图模型进行比较时,您将更好地理解此范围的工作原理。
现在是时候更改“Home”视图文件夹下的“Index”视图并集成我们的部分视图了。
更新 Index 视图
将此标记添加到“Index”视图中,替换其当前标记。
下一步我们将创建“KnockoutModels/StudentRegisterViewModel.js”,现在只需添加引用。
@{
ViewBag.Title = "Student Register";
}
<script src="~/Scripts/knockout-3.1.0.js"></script>
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/KnockoutModels/StudentRegisterViewModel.js"></script>
<div class="page-header">
<h2 class="text-center">Student Registration</h2>
</div>
<div class="row">
<div class="col-md-4">
<div class="panel panel-info">
<div class="panel-heading">
<h2 class="panel-title">Register new student</h2>
</div>
<div class="panel-body" data-bind="with: addStudentViewModel">
@Html.Partial("_RegisterStudent")
</div>
</div>
</div>
<div class="col-md-8">
<div class="panel panel-primary">
<div class="panel-heading">
<h2 class="panel-title">Registerd Students</h2>
</div>
<div class="panel-body" data-bind="with: studentListViewModel">
@Html.Partial("_StudentsList")
</div>
</div>
</div>
</div>
通过声明 [data-bind="with: addStudentViewModel"] 表达式,我们告诉该部分视图使用“addStudentViewModel”子视图模型。因此,Index 视图模型包含两个部分视图的子视图模型。
让我们为 index 视图创建一个 JavaScript 视图模型文件。
创建视图模型
创建一个名为“KnockoutModels”的文件夹,并添加一个名为“StudentRegisterViewModel.js”的 JavaScript 文件。
将此代码添加到“StudentRegisterViewModel.js”文件中。请仔细阅读此代码。它包含所有视图逻辑和必要的 Web API 调用。
var studentRegisterViewModel;
// use as register student views view model
function Student(id, firstName, lastName, age, description, gender) {
var self = this;
// observable are update elements upon changes, also update on element data changes [two way binding]
self.Id = ko.observable(id);
self.FirstName = ko.observable(firstName);
self.LastName = ko.observable(lastName);
// create computed field by combining first name and last name
self.FullName = ko.computed(function() {
return self.FirstName() + " " + self.LastName();
}, self);
self.Age = ko.observable(age);
self.Description = ko.observable(description);
self.Gender = ko.observable(gender);
// Non-editable catalog data - should come from the server
self.genders = [
"Male",
"Female",
"Other"
];
self.addStudent = function () {
var dataObject = ko.toJSON(this);
// remove computed field from JSON data which server is not expecting
delete dataObject.FullName;
$.ajax({
url: '/api/student',
type: 'post',
data: dataObject,
contentType: 'application/json',
success: function (data) {
studentRegisterViewModel.studentListViewModel.students.push(new Student(data.Id, data.FirstName, data.LastName, data.Age, data.Description, data.Gender));
self.Id(null);
self.FirstName('');
self.LastName('');
self.Age('');
self.Description('');
}
});
};
}
// use as student list view's view model
function StudentList() {
var self = this;
// observable arrays are update binding elements upon array changes
self.students = ko.observableArray([]);
self.getStudents = function () {
self.students.removeAll();
// retrieve students list from server side and push each object to model's students list
$.getJSON('/api/student', function (data) {
$.each(data, function (key, value) {
self.students.push(new Student(value.Id, value.FirstName, value.LastName, value.Age, value.Description, value.Gender));
});
});
};
// remove student. current data context object is passed to function automatically.
self.removeStudent = function (student) {
$.ajax({
url: '/api/student/' + student.Id(),
type: 'delete',
contentType: 'application/json',
success: function () {
self.students.remove(student);
}
});
};
}
// create index view view model which contain two models for partial views
studentRegisterViewModel = { addStudentViewModel: new Student(), studentListViewModel: new StudentList() };
// on document ready
$(document).ready(function () {
// bind view model to referring view
ko.applyBindings(studentRegisterViewModel);
// load student data
studentRegisterViewModel.studentListViewModel.getStudents();
});
就是这样。
运行应用程序查看效果。您应该看到类似这样的内容。
希望这个快速指南能帮助您快速上手 knockoutJS。您可以在 http://learn.knockoutjs.com 找到更多关于 knockoutjs 的信息和吸引人的教程。
历史
初始发布:2014 年 5 月 5 日