KnockoutJS with ASP.NET MVC/Web Api and Bootstrap

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 API




 Add KnockoutJS to project

Using nuget package manager add reference to knockoutjs libraries




Create Application Database

create database file under app_data folder and with visual studio provided tools, create students table. please refer to screenshot for data-table fields








Add Entity Data Model

Create a folder called 'EntityDataModel' and add ADO.NET Entity Data Model called StudentModel.edmx to that folder

 
Select 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 controllers

add 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" />
In here for tbody tag we are using [data-bind="foreach: students"] binding expression
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>
by stating [data-bind="with: addStudentViewModel"] expression, we are saying to use 'addStudentViewModel' sub view-model for the partial view. so Index view-model contain two sub view-models for partial views.

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.

0 comments:

Post a Comment