Introduction to Knockout.js and CRUD Operations in ASP.Net Web Forms Using Knockout.JS

This is my opportunity to explain the development paradigm from a basic to an advanced level my way. Knockout.JS is an example. This article includes an introduction to Knockout, MVVM and the observer pattern with an ASP.Net web forms application that performs CRUD operations.

Introduction

The development paradigm has been changing rapidly for the last few years. The out-of-box technologies have been introduced to develop the applications as fast, scalable, with an extensible structure, easy to maintain and easy to use. "knockout.js" is an example of such emerging technologies. This is my opportunity to explain the concept and topic my way.

We'll be explaining the technology from a basic to an advanced level by just following a road-map.
 
Our Road-map

Road-map
 
We'll learn "knockout.js" in the following three parts:

Part 1: Introduction to "knockout.js" and CRUD Operations in ASP.Net Web Forms using "knockout.js"

We'll explain this part beginning with an introduction to Knockout, MVVM and the Observer Pattern. Then by setting up a basic environment in knockout.js, create an ASP.Net web forms application that does CRUD operations.
 
Knockout
 
In today's quickly changing trends, development of data-driven apps depends largely on JavaScript and JavaScript based libraries such as jQuery. Client-side programming appears to become complex and it is complex because the user interface becomes more and more rich. In scenarios like this the data binding and dependency tracking are highly desirable in the applications for further extensibility of the application. Knockout JS fulfils these rich requirements on the client side programming and makes a developer's life easy and joyfull. Let's explain KO in detail.

Knockout

The "knockout.js" (KO) file is basically a JavaScript library that enables Declarative Bindings using an "Observable" ViewModel on the client (browser) using an Observer Pattern approach, enabling the UI to bind and refresh itself automatically whenever the bound data is modified. The "knockout.js" file provides its own templating pattern that helps us to bind our view model data easily. KO works on the MVVM pattern, in other words Model-View-ViewModel.

As the architecture is shown, Views interact with View Models in a two-way binding manner, in other words when the model is changed the view updates itself and when the view is updated, the model also updates itself instantaneously.

KO provides the following 3 most important features:

  • Automatic Refresh of UI
  • Two way binding
  • Templating

The entire idea of KO derives from these three major functionalities. KO also helps in developing single-page applications (SPAs). SPAs are an out-of-the box new way of developing Rich Internet Applications (RIAs) in today's era.
 
Model-View-View Model (MVVM)
 
When we develop a rich UI internet based application, we create views (UI like HTML and aspx pages) using server controls, HTML controls and then extend our application by writing business logic behind those views like event handling, property binding and creation of entities. This approach increases complexities when an application is too large. Here we require separation of concerns and maintainability of the application, especially on the client side.
 
The MVVM pattern includes the following three key parts:
 

  1. Model (business rules, data access, model classes, the data displayed in a UI)
  2. View (User Interface (such as HTML, aspx, cshtml and so on))
  3. ViewModel (event handling, binding and business logic)

Model refers to our application data and domain model, in other words entities. In a traditional ASP.NET web application, the data is basically stored inside a database or files and the UI fetches the data using a client-server request like Ajax or directly bound to itself.

View Model contains the User Interface level operations/methods/functions, performed on model data to bind the outcome to the view. The operations include business logic validations and checks to be performed before binding data to the UI. View models act as an interface between model and views and acts as a wrapper over the model prior to binding to the Views.

View is the user interface of our application. View talks to the View Model to invoke certain methods/operations as explained above. View gets updated automatically whenever data from the View Model changes.
 
MVVM provides a clear separation of concerns between the User Interface (UI) and the business logic.
In the MVC pattern, a view acts as the broker agent between the Model (the data displayed in the View) and the Controller (server-side endpoint that takes data from the View and performs some action on that data and provides a response).
 
Observables for two way binding
 
KO provides Observables in the library to be bound to UI elements and simultaneously code is written to view models, so that when the view updates the data the model updates itself and vice versa. For example, in the following code:

<tr>

    <td>Batch :</td>

    <td><input data-bind="value: Batch" /></td>

    <td><span data-bind="text: Batch" /></td>

</tr>

<tr>

    <td>Address :</td>

    <td><input data-bind="value: Address" /></td>

    <td><span data-bind="text: Address" /></td>

</tr>

<tr>

    <td>Class :</td>

    <td><input data-bind="value: Class" /></td>

    <td><span data-bind="text: Class" /></td>

</tr>
 
The code above shows a part of a view, you can see the elements are bound to properties like text and value, these properties are provided by KO, and the right side of these properties are property key names that are bound in view-models using observables as shown below.
       

var self = this;

self.Batch = ko.observable();

self.Address = ko.observable();

self.Class = ko.observable();


So this would be the code in the View model; we'll be explaining all this in detail.
 
Note: data-bind is an HTML5 attribute.
 
Setting up Environment in Visual Studio for KO
 
We go step-by-step to set up a Knockout JavaScript environment in Visual Studio.
The prerequisite is that Visual Studio must be a version equal to or 12. I am using Visual Studio 2013 Express.
 
Step 1:
Open Visual Studio and create a simple ASP.Net application, I have given it the name KOSetup.

Creating Application
 
Step 2:  Right-click on the project, and in the context menu select "Manage Nuget packages" to install jQuery and KO.

Nuget packages
 
Step 3:  Type "jQuery" in the search text box to get the latest compatible jQuery library. Click "install" to install the library.

jQuery library
 
Step 4:  In a similar fashion, search 'knockout' in the search TextBox and install the knockoutjs library into your application.

knockoutjs library

Step 5: Our solution will look as in the following. We have a folder created named "Scripts" and that contains jQuery and Knockout libraries.

contains jQuery and knockout libraries
 
Step 6:  Now right-click the project and add and aspx page. I named that page "LearnKO.aspx".

aspx page
 
Step 7: Similarly create a JavaScript file and add that to the project. I named that file "LearnKO.js".

javascript file

Step 8: Open the "learnKO.js" file and drag the jQuery file and "knockout.js" library file to the "LearKO.js" file, we see in the following picture that references for both files are created on the JavaScript file. We did this because to provide intellisense support for jQuery and Knockout on our "LearnKO.js" file.

jQuery file and knockout.js library file
 
Step 9: Write the document.ready fuction of jQuery in our LearnKO.js file. The document.ready function is fired when our HTML document object model has been loaded into the browser.

document ready fuction
 
This is all we need to do to set up Knockout. Now we know how to set up the initial environment to use "knockout.js" in our application.

setup knockout
                                                                     
We proceed now to create the application, talk to a database and create a template and view model.

Creating Knockout application

 
Step 10:
For communication with a database, add the Entity Framework library in the same manner as we added jQuery and KO. Installing the Entity Framework library will add the Entity Framework DLL to the project. We'll talk to the database using the Entity Framework of Microsoft. Alternatively there are many ways to intervace with a database, like ADO.Net, LINQ to SQL and so on. But first things first, create a database; you need it to use SQL Server. I've provided a script for that.

create a data base

EntityFrameWork

Step 11: Right-click the project and add an ADO.NET Entity data Model, click "Add" and use the following procedure:

ADO.NET Entity data Model

Step 12: The following is the second step of the Entity Data Model, you can choose model contents from the database you already created. So select "Generate From database". Click Next.

Entity Data Model

Step 13: Choose the table you want to add, in other words the Student table, as shown below in the figure. Name the model "LearningKOModel". Click "Finish".

Student table

Step 14: We get certain files in our solution, like context and tt files. We also get a "Student.CS" file, that will act as our server-side domain model. The context class contains the data communication methods of the Entity Framework.

context class

Step 15:
Write three methods using the Entity Framework in our "aspx.cs" page. One method fetches all the students and another method saves and deletes students to/from the database, as shown below. Mark them as web method so that they could be called from the client side.

web method
 
Code

      #region Public Web Methods.

        /// <summary>

        /// Gets Student Details

        /// </summary>

        /// <returns></returns>

        [WebMethod]

        public static Student[] FetchStudents()

        {

            LearningKOEntities dbEntities = new LearningKOEntities();

            var data = (from item in dbEntities.Students

                        orderby item.StudentId

                        select item).Take(5);

            return data.ToArray();

        }

 

        /// <summary>

        /// Saves Student Details

        /// </summary>

        /// <param name="data"></param>

        /// <returns></returns>

        [WebMethod]

        public static string SaveStudent(Student[] data)

        {

            try

            {

                var dbContext = new LearningKOEntities();

                var studentList = from dbStududent in dbContext.Students select dbStududent;

                foreach (Student userDetails in data)

                {

                    var student = new Student();

                    if (userDetails != null)

                    {

                        student.StudentId = userDetails.StudentId;

                        student.FirstName = userDetails.FirstName;

                        student.LastName = userDetails.LastName;

                        student.Address = userDetails.Address;

                        student.Age = userDetails.Age;

                        student.Gender = userDetails.Gender;

                        student.Batch = userDetails.Batch;

                        student.Class = userDetails.Class;

                        student.School = userDetails.School;

                        student.Domicile = userDetails.Domicile;

                    }

                    Student stud=(from st in studentList where st.StudentId==student.StudentId select st).FirstOrDefault();

                    if (stud == null)

                        dbContext.Students.Add(student);

                    dbContext.SaveChanges();

                }

                return "Data saved to database!";

            }

            catch (Exception ex)

            {

                return "Error: " + ex.Message;

            }

        }

 

        /// <summary>

        /// Deletes Student Details

        /// </summary>

        /// <param name="data"></param>

        /// <returns></returns>

        [WebMethod]

        public static string DeleteStudent(Student data)

        {

            try

            {

                var dbContext = new LearningKOEntities();

                var student = dbContext.Students.FirstOrDefault(userId => userId.StudentId == data.StudentId);

                if (student != null)

                {

                    if (student != null)

                    {

                        dbContext.Students.Remove(student);

                        dbContext.SaveChanges();

                    }

                }

                return "Data deleted from database!";

 

            }

            catch (Exception ex)

            {

                return "Error: " + ex.Message;

            }

        }

        #endregion

  
Step 16: Open the aspx page we created and add the following code to it, the code provides templates in HTML bound to model properties, one to add a Student and the other to display a Student List.
 

<table style="width: 100%;">

    <tbody>

        <tr>

            <th style="width: 100px;">Property Name</th>

            <th style="width: 100px;">Enter Value</th>

            <th style="width: 100px;">

                Example of two Way Binding

            </th>

        </tr>

    </tbody>

    <tr>

        <td>Student ID (int):</td>

        <td><input data-bind="value: StudentId" /></td>

        <!--,valueUpdate:'keypress'-->

        <td><span data-bind="text: StudentId" /></td>

    </tr>

    <tr>

        <td>First Name :</td>

        <td><input data-bind="value: FirstName" /></td>

        <td><span data-bind="text: FirstName" /></td>

    </tr>

    <tr>

        <td>Last Name :</td>

        <td><input data-bind="value: LastName" /></td>

        <td><span data-bind="text: LastName" /></td>

    </tr>

    <tr>

        <td>Student Age (int) :</td>

        <td><input data-bind="value: Age" /></td>

        <td><span data-bind="text: Age" /></td>

    </tr>

    <tr>

        <td>Gender :</td>

        <td><select data-bind="options: Genders, value: Gender, optionsCaption: 'Select Gender...'"></select></td>

        <td><span data-bind="text: Gender" /></td>

    </tr>

    <tr>

        <td>Batch :</td>

        <td><input data-bind="value: Batch" /></td>

        <td><span data-bind="text: Batch" /></td>

    </tr>

    <tr>

        <td>Address :</td>

        <td><input data-bind="value: Address" /></td>

        <td><span data-bind="text: Address" /></td>

    </tr>

    <tr>

        <td>Class :</td>

        <td><input data-bind="value: Class" /></td>

        <td><span data-bind="text: Class" /></td>

    </tr>

    <tr>

        <td>School :</td>

        <td><input data-bind="value: School" /></td>

        <td><span data-bind="text: School" /></td>

    </tr>

    <tr>

        <td>Domicile :</td>

        <td>

            <select data-bind="options: Domiciles, value: Domicile, optionsCaption: 'Select Domicile...'"></select></td>

        <td><span data-bind="text: Domicile" /></td>

    </tr>

    <tr>

        <td colspan="3"><button type="button" data-bind="click: AddStudent">Add Student</button>

            <button type="button" data-bind="click: SaveStudent">Save Student To Database</button>

        </td>

    </tr>

</table>

</div>

<div style="width: 70%; float: left; display: inline-block;">

    <h2>List of Students</h2>

    <table style="width: 100%;" data-bind="visible: Students().length > 0" border="0">

        <tr>

            <th>Student Id</th>

            <th>First Name</th>

            <th>Last Name</th>

            <th>Age</th>

            <th>Gender</th>

            <th>Batch</th>

            <th>Address</th>

            <th>Class</th>

            <th>School</th>

            <th>Domicile</th>

        </tr>

        <tbody data-bind="foreach: Students">

            <tr>

                <td><span data-bind="text: StudentId" /></td>

                <td><input data-bind="value: FirstName" /></td>

                <td><input data-bind="value: LastName" /></td>

                <td><input data-bind="value: Age" /></td>

                <td><select data-bind="options: $root.Genders, value: Gender"></select></td>

                <td><input data-bind="value: Batch" /></td>

                <td><input data-bind="value: Address" /></td>

                <td><input data-bind="value: Class" /></td>

                <td><input data-bind="value: School" /></td>

               <td><select data-bind="options: $root.Domiciles, value: Domicile"></select></td>

                <td><a href="#" data-bind="click: $root.DeleteStudent">Delete</a></td>

            </tr>

        </tbody>

    </table>
 
There are two HTML tables, one for adding a student to the database and the other showing all the students having a delete anchor link to delete the student, these template properties will be bound in the view model, where we write a method to communicate with the database and call the Web Methods we created in the "aspx.cs" page.The Viewmodel also contains observables to be bound to these properties.
 
Step 17: Now it's time to create the ViewModel. Open the "learnKO.js" file and add code to fetch, save and delete students and observables bound to properties bound on controls of the HTML page.
 

/// <reference path="jquery-2.0.3.min.js" />

/// <reference path="knockout-3.0.0.js" />

function Student(data) {

  this.StudentId = ko.observable(data.StudentId);

  this.FirstName = ko.observable(data.FirstName);

  this.LastName = ko.observable(data.LastName);

  this.Age = ko.observable(data.Age);

  this.Gender = ko.observable(data.Gender);

  this.Batch = ko.observable(data.Batch);

  this.Address = ko.observable(data.Address);

  this.Class = ko.observable(data.Class);

  this.School = ko.observable(data.School);

  this.Domicile = ko.observable(data.Domicile);

}

function StudentViewModel() {

    var self = this;

    self.Domiciles = ko.observableArray(['Delhi', 'Outside Delhi']);

    self.Genders = ko.observableArray(['Male', 'Female']);

    self.Students = ko.observableArray([]);

    self.StudentId = ko.observable();

    self.FirstName = ko.observable();

    self.LastName = ko.observable();

    self.Age = ko.observable();

    self.Batch = ko.observable();

    elf.Address = ko.observable();

    self.Class = ko.observable();

    self.School = ko.observable();

    self.Domicile = ko.observable();

    self.Gender = ko.observable();

    self.AddStudent = function () {

    self.Students.push(new Student({

         StudentId: self.StudentId(),

         FirstName: self.FirstName(),

         LastName: self.LastName(),

         Domicile: self.Domicile(),

         Age: self.Age(),

         Batch: self.Batch(),

         Address: self.Address(),

         Class: self.Class(),

         School: self.School(),

         Gender: self.Gender()

   }));

       self.StudentId(""),self.FirstName(""),self.LastName(""),self.Domicile(""),self.Age(""),self.Batch(""),self.Address(""),self.Class(""),self.School(""),self.Gender("")

   };

   self.DeleteStudent = function (student) {

   $.ajax({

        type: "POST",

        url: 'LearnKO.aspx/DeleteStudent',

        data: ko.toJSON({ data: student }),

        contentType: "application/json; charset=utf-8",

        success: function (result) {

             alert(result.d);

             self.Students.remove(student)

       },

       error: function (err) {

             alert(err.status + " - " + err.statusText);

      }

    });

  };

  self.SaveStudent = function () {

     $.ajax({

            type: "POST",

             url: 'LearnKO.aspx/SaveStudent',

             data: ko.toJSON({ data: self.Students }),

             contentType: "application/json; charset=utf-8",

             success: function (result) {

                  alert(result.d);

             },

             error: function (err) {

                  alert(err.status + " - " + err.statusText);

              }

          });

       };

       $.ajax({

              type: "POST",

              url: 'LearnKO.aspx/FetchStudents',

              contentType: "application/json; charset=utf-8",

              dataType: "json",

              success: function (results) {

              var students = $.map(results.d, function (item) {

                    return new Student(item)

              });

              self.Students(students);

              },

              error: function (err) {

                  alert(err.status + " - " + err.statusText);

              }

        })

   }

   $(document).ready(function () {

       ko.applyBindings(new StudentViewModel());

});
 
We create StudentViewModel() as our primary view model JavaScript function, that contains all the business logic and operations.

We bind this View model on the document ready function by the KO method named "applyBindings".This initializes our view model, ko.applyBindings(new StudentViewModel());

The function Student(data) contains observables bound to model properties.

We can create observable arrays of Domiciles and genders to bind to the dropdown list of our HTML. Ko provides these observables and other such properties to bind to the model.
 
. observable: Used to define model/entity properties. If these properties are bound with the user interface and when the value for these properties are updated, the UI elements are automatically bound with these properties and will be updated with the new value instantaneously.
 
For example, in the
following, StudentId is the observable property; KO represents an object for the "knockout.js" library.

this.StudentId = ko.observable("1");
 
The value of the observable is read as:

var id= this. StudentId ();
 
. observableArray: observableArray represents a collection of data elements that require notifications. It's used to bind with the List kind of elements.

For example:
 
this.Students = ko.observableArray([]);
 
. applyBindings: This is used to activate Knockout for the current HTML document or a specific UI element in a HTML document. The parameter for this method is the view-model that is defined in JavaScript. This ViewModel contains the observable, observableArray and various methods.
 
Various other types of binding are used in this article:
 
o click: Represents a click event handler added to the UI element so that the JavaScript function is called.
 
o value: This represents the value binding with the UI element's value property to the property defined into the ViewModel.
 
The value binding should be used with <input> , <select> , <textarea>.
 
o visible: This is used to hide or unhide the UI element based upon the value ed to it's binding.
 
o Text: This represents the text value of the parameter ed to the UI element.
 
Step 18: Include JavaScript files and stylesheet (you can create your own stylesheet file) to the head section of the aspx page.

js files and stylesheet
 
Step 19: Press F5 to run the application, and we'll be shown a page having HTML controls as follows.

run the application
 
The list of students shows the list of students from the database, bound to the viewmodel's event.

Try to create a new student and add a student then save it to the database, it will automatically be added to the right hand side list.

We can see that the Domicile and Genders drop down lists are bound to our Viewmodel's properties.
 
Note: Do not give a string in the StudentId and Age since no validation is put on those fields, the code may break.
 
Create Student

enter Student Detail
 
Student added to list and database

Student added to list and database
 
Job Done !

Done

Now you can say that you have become a "knockout.js" developer.
 
Conclusion
 
We learned a lot in this article about how to set up "knockout.js" in Visual Studio, many theorys and also created a sample application just to get hands-on exerince with the concept. There are numerous articles and blogs related to this concept. You can explore more and more to learn. In the next article I'll explain the creation of a sample application and performing CRUD operations in MVC4 with Knockout JavaScript and Entity Framework, now pat your back for having done a great job by learning a new concept.

learning a new concept

Note: a few of the images in this article were obtained via Google search. You can follow my articles at csharppulse.blogspot.in.