Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Jumpstart KnockoutJS with ASP.NET MVC/Web Api

0.00/5 (No votes)
7 May 2014 1  
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.

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

<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

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

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

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here