I heard numbers of rumors about knockoutjs but didn't give a try until now. After doing a simple application i realized that this is very smart technology which can do sleek and responsive applications with integrating latest technologies.
I'm using Visual Studio 2013 with .NET 4.5 to complete this tutorial. but this is not must you can complete with VS 2010 or later with .NET 4.5
What we are going to do.
Let's start by creating simple ASP.NET MVC application with WebApi data service. and then we'll use LocalDB to create sample database and create Entity Data Model for that database. We'll add Web API Controller to expose our data and then we can use knockoutjs to tailor data service to our MVC pages. we are using bootstrap to get neat UI styles which integrated to our MVC template already.OK, let's start.
1. Project Setup
Create ASP.NET MVC Application.
Select ASP.NET Web Application template under .NET framework 4.5 and tick Web API on second page to add core reference for Web APIAdd KnockoutJS to project
Using nuget package manager add reference to knockoutjs librariesCreate Application Database
Add Entity Data Model
Create a folder called 'EntityDataModel' and add ADO.NET Entity Data Model called StudentModel.edmx to that folderSelect existing StudentDatabase database and select Student table. EF model should look like this after generated.
Create Web API controller
Right click on controllers folder and click add --> Controller.
Select Web API 2 Controller with read/write actions template and click add.
Name this controller as StudentController.cs
Now we have project setup for the solution. We will do the implementation to achieve required functionality.
2. Implementation
Lets add new class called 'StudentRepository' under 'Models' folder which enable us to access EF data models more easily.
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(); } } } }
OK, Let's update our Web API controller to do basic read/ create/ delete operations on students table with help of StudentRepository class.
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; } } }
Create MVC views
for displaying registered student list and adding registered student let's create two partial views.later we can add these two partial views to Index view which will display student list and provide functionality to register new student.
Register partial view
add new partial view called _RegisterStudent.cshtml. You can added this under 'Home' views folder because this is not shared among controllersadd below markup.
<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" />
Here we're using bootstrap styles for form styling (role="form", class="form-group", class="form-control", class="btn btn-primary" )
Also we add knockout model data binding attributes (data-bind=""). this bind knockout view model to html element property which is specified in binding expression.
For example [data-bind="value: FirstName"] says bind 'FirstName' property from view model to value of this element.
if you look at button's binding expression, it's look like [data-bind="click: addStudent"]. this states bind click event of the button to addStudent function of the view model.
We will specify view-model when creating our view-model java-script file.
Listing Partial view
OK, lets create our listing partial view.Add another partial view called _StudentsList.cshtml and add following markup
<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" />
so we have students collection in our view model and expression will create table row for each of this students collection element.
also button's binding have [ data-bind="click: $parent.removeStudent"] expression. which state removeStudent function is reside at parent which outside the view-model context. you will have better idea when comparing this with the view-model.
Lets create javascript viewmodel file for the index view.
Create folder named 'KnockoutModels' and add javascript file named 'StudentRegisterViewModel.js'
OK, it's time change 'Index' view under 'Home' view folder and integrate our partial views.
Update Index view
add this markup to 'Index' view by replacing it's current markup.@{ 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>
It's time to populate our java-script view-model class.
Add this code to 'StudentRegisterViewModel.js' file. please go through this code carefully. this contain all the view logic and necessary Web API calls.
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(); });
So That's it.
run the application and see. you should have something like this.
Thanks for following this tutorial. Hope you gain some basic knowledge on knockoutjs.