Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#

Jumpstart KnockoutJS with ASP.NET MVC/Web Api

Rate me:
Please Sign up or sign in to vote.
4.55/5 (27 votes)
7 May 2014CPOL4 min read 96.6K   4.1K   44   17
use knockout.js in ASP.net MVC pages with Web API data services

Introduction

Knockoutjs is at the center of attention for a quite long time. It is a client-side data-binding JavaScript library which help us to create clean data models and responsible for events and data-binding. knockoutjs reflect changes on views real-time and update only changed areas.

Using knockoutjs you can build sleek and responsive web applications with today's popular technologies.

For this demonstration, let's create ASP.NET MVC web application called student-register and use knockoutjs for client-side data binding.

We are using ASP.NET Web API service to talk to the server side persistent data store.

Using the code

Let's begin with ASP.NET MVC (5) application with Web API and create LocalDB database for data store.

Then Let's create Entity Data Model for our database and expose data via Web API service.

Finally, we'll consume Web service from client side and use knockoutjs to bind data to our views.

We are using integrated bootstrap styles here to achieve clean, simple UIs

Project Setup

Create ASP.NET MVC Application with Web API.

Add new ASP.NET web application under .NET framework 4.5. On second page, tick Web API to add core reference for Web API services.

Add KnockoutJS libraries to project.

Using nuget package manager, add knockoutJS libraries to the project.

Create Application Database.

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

Add Entity Data Model

Add a new folder called 'EntityDataModel' to the solution and add ADO.NET Entity Data Model called StudentModel.edmx to that folder.

Select existing Student database and select students table. for the Entitiy Data Model. 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. In next steps, we will do the implementation to achieve required functionality.

Implementation

Lets add new class called 'StudentRepository' under 'Models' folder which enable us to access EF data models more easily.

C#
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();
            }
        }
    }
} 

Next, Let's update our Web API controller to do basic read/ add/ 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

Lets create two partial views for display registered student list and register new student.

later we can add these two partial views to Index view which will display student list and provide functionality to register new student as well.

Register Student partial view

add new partial view called _RegisterStudent.cshtml. You may add this under 'Home' views folder since this is not shared among controllers

_RegisterStudent.cshtml

ASP.NET
<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" /> 

We're using bootstrap styles here for styling form elements (role="form", class="form-group", class="form-control", class="btn btn-primary" )

Also we add knockout 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

Lets create our listing partial view.

Add another partial view called _StudentsList.cshtml with following markup

_StudentsList.cshtml

ASP.NET
<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 tbody tag we are using [data-bind="foreach: students"] binding expression. as name implies it's an iterative expression.

We have collection of student objects called students in our view model and expression will create table row for each of this student object.

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 get better idea how this scope works when comparing this with the actual view-model.

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.

We'll create 'KnockoutModels/StudentRegisterViewModel.js' in next step, for now just add the reference.

ASP.NET
@{
    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.

Lets create javascript viewmodel file for the index view.

Create ViewModel

Create folder named 'KnockoutModels' and add javascript file named 'StudentRegisterViewModel.js'

Add this code to 'StudentRegisterViewModel.js' file. please go through this code carefully. this contain all the view logic and necessary Web API calls.

JavaScript
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.

Hope this quick guide will help you to get a jump start on knockoutJS. you can find more info and attractive tutorials on knockoutjs at http://learn.knockoutjs.com

History

Initial Post: 5th May 2014

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Navantis
Sri Lanka Sri Lanka
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionI need Knockout Validation in your Jumpstart KnockoutJS with ASP.NET MVC/Web Api Pin
Member 1027911411-Jan-17 0:14
Member 1027911411-Jan-17 0:14 
QuestionReference error: Id is not defined Pin
MayurPadhiyar22-Aug-16 21:01
MayurPadhiyar22-Aug-16 21:01 
AnswerRe: Reference error: Id is not defined Pin
Ciaz18-Jul-18 5:15
Ciaz18-Jul-18 5:15 
QuestionValidation Pin
Deepesh Kapadia13-Aug-15 20:02
Deepesh Kapadia13-Aug-15 20:02 
Questionneed help Pin
Member 118404529-Aug-15 21:54
Member 118404529-Aug-15 21:54 
GeneralVery helpful article Pin
bayswagger20-Jun-15 20:54
bayswagger20-Jun-15 20:54 
Questionvery nice article Pin
Member 114367918-Feb-15 23:25
Member 114367918-Feb-15 23:25 
GeneralVery nice article Pin
Sajith Dilhan23-Dec-14 18:12
Sajith Dilhan23-Dec-14 18:12 
QuestionHow to Edit Pin
Member 1085419029-Oct-14 7:51
Member 1085419029-Oct-14 7:51 
QuestionVery Nice article i will give 5 out of 5 Pin
Member 1005310124-Jul-14 4:25
Member 1005310124-Jul-14 4:25 
Very Nice article i will give 5 out of 5
Questionweb api: passing custom data during authentication Pin
Dmitry A. Efimenko6-May-14 8:31
Dmitry A. Efimenko6-May-14 8:31 
AnswerRe: web api: passing custom data during authentication Pin
Lilanga6-May-14 18:24
professionalLilanga6-May-14 18:24 
Questionsource code file link broken Pin
Tridip Bhattacharjee5-May-14 20:55
professionalTridip Bhattacharjee5-May-14 20:55 
AnswerRe: source code file link broken Pin
Lilanga6-May-14 17:53
professionalLilanga6-May-14 17:53 
GeneralMy vote of 3 Pin
Er. Puneet Goel5-May-14 18:06
professionalEr. Puneet Goel5-May-14 18:06 
GeneralRe: My vote of 3 Pin
Red Feet6-May-14 10:08
Red Feet6-May-14 10:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.