Building a Knockout.js based client for Web API services in an ASP.NET MVC app

ASP.NET MVC is now a popular web development platform and often first time users have questions regards to implementation of Data Bound behavior in MVC. This is understandably because most Customers at some point have used ASP.NET Web Forms earlier and are comfortable with the event driven model that it provides.

A common question is how to create an editable list of data allowing users to Edit and Save existing data. The default MVC scaffolding in Visual Studio provides us with a way to implement this where Create/Edit/Delete are implemented on the server side and each action has a separate UI. Today we will see how we can implement it using jQuery and a lightweight client side library knockout.js (also referred to as KO).

On the server, we will use ASP.NET Web API to expose the database services that will be accessed by JavaScript. Needless to say we will use AJAX to communicate with the backend Services.

For any ‘List’ of data there is a need for implementing some kind of template or repeater behavior so that we can define the UI for one row of data and the library/framework takes that template and repeats it for the entire list. We know how to do this in ASP.NET MVC using Razor syntax. Today we’ll see how implement the repeater UI using KO’s templating engine. We will also see how we can define separate templates for Display and Edit modes.

Building the Knockout Sample

With the problem statement out of the way, let’s build the application step-by-step.

Setting up the DB

For the sample I am using a SQL Server Database called Company. The Table is EmployeeInfo you can generate it using the following script:

Create database Company
GO
USE [Company]
GO
/****** Object:  Table [dbo].[EmployeeInfo]    Script Date: 8/21/2012 3:57:12 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[EmployeeInfo](
[EmpNo] [int] IDENTITY(1,1) NOT NULL,
[EmpName] [varchar](50) NOT NULL,
[Salary] [decimal](18, 0) NOT NULL,
[DeptName] [varchar](50) NOT NULL,
[Designation] [varchar](50) NOT NULL,
CONSTRAINT [PK_EmployeeInfo] PRIMARY KEY CLUSTERED
(
  [EmpNo] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
GO


The Web App

Step 1: Open VS2012 and create a new Empty MVC application. Name it as ‘MVC40_Editable_DataList’. On this project, right click and select Manage NuGet Package and install jQuery and KnockOut.js.

Step 2: In the Models folder add an ADO.NET EF. Complete the Wizard for Company Database and select an EmployeeInfo table. You will get the mapping with the table as below:

ef-schema

Step 3: In the Controllers folder add a new API controller of name EmployeeInfpAPI based upon the ADO.NET EF added in the previous task as below:

add-new-api-controller

Here you will get ready code with the HTTP GET|POST|PUT|DELETE methods. These methods will be called using jQuery ajax methods.

Step 4: In the controller add a new Empty MVC Controller, name it as EmployeeInfoController as below:

add-new-mvc-controller

Step 5: In the EmployeeInfo Controller, add the action method of name ‘ListData’. This method will simply return the ViewResult as below:

public ActionResult ListData()
{
return View();
}


Step 6: Generate a View from the above action method. Name it as ListData.cshtml and use the Empty template for scaffolding. We will use this view to add our code for UI, templates etc.

Step 7: On the ListData.cshtml, add the below script reference and styles:

<style type="text/css">
table {
  width:400px;
}
td {
  width:200px;
}
</style>
<script src="~/Scripts/jquery-1.9.1.min.js"></script>
<script src="~/Scripts/knockout-2.2.1.js"></script>


Step 8: Add the below script on the view:

<script type="text/javascript">
var Employees = [];
var self = this;
self.Data = ko.observableArray([]);
getrecords();
  
//Function To Retrive Data from using ajax call to WEB API
function getrecords() {
  $.ajax({
   type: "GET",
   url: "http://localhost:1902/api/EmployeeInfoAPI",
   contentType: "application/json; charset=utf-8",
   dataType: "json",
   success: function (data) {
   //  Employees = getDataInArray(data);
    Employees = data;
    self.Data(data);
   },
   error: function (err) {
    alert("Error : " + err.status + "   " + err.statusText);
   }
  });
};     


alert("Loading Data");

var EmpModel = {
  Emps:self.Data(),
  viewRecordtemplate: ko.observable("viewtemplate"),
  editRecordtemplate: ko.observable()
};

EmpModel.templateDisplayed = function (t) {
  return t === this.editRecordtemplate() ? 'edittemplate' :
   this.viewRecordtemplate();
}.bind(EmpModel);

//Method to Save the Updated Record
EmpModel.Save = function (t) {
  var Employee = {};
  Employee.EmpNo = t.EmpNo;
  Employee.EmpName = t.EmpName;
  Employee.Salary = t.Salary;
  Employee.DeptName = t.DeptName;
  Employee.Designation = t.Designation;
  $.ajax({
   type: "PUT",
   url: "
http://localhost:1902/api/EmployeeInfoAPI/" + Employee.EmpNo,
   data: Employee,
   success: function (data)
   {
    alert("Record Updated Successfully " + data.status);
    EmpModel.resettemplate(); //Reset the Template
   },
   error: function (err)
   {
    alert("Error Occured, Please Reload the Page and Try Again " +
     err.status);
    // location.reload(); //REload the Page
    EmpModel.resettemplate(t); //Reset the Template
   }
  });
};


//Method to Reset theEdit View
EmpModel.resettemplate = function (t) {
  this.editRecordtemplate("viewtemplate");
};
ko.applyBindings(EmpModel);
</script>


The above code defines what is referred to as the ViewModel for KO. It defines JavaScript array for Employees and sticks it into an Observable property. An Observable in KO is a function that watches over a variable or array and monitors changes. When the value of the observable or count of the observable array changes. On change KO and automatically update UI elements to which the observable was bound to.

The method ‘getrecords’ makes an ajax call to Web API and fetchs the EmployeeInfo from the server. When the Ajax call is successful the received data is the stored in the ObservableArray of name ‘Data’. To implement a nice definitive progressbar you can use this article.

The ViewModel of name ‘EmpModel’, defines the ‘Emps’ entity which will store the data from ‘Data’ observableArray.

The Observables - viewRecordtemplate and editRecordtemplate are declared for storing the id’s of the HTML template which we will be declaring shortly.

The function, ‘EmpModel.templateDisplayed’ will be used to decide which HTML template is to be displayed when page is loaded. The function ‘empModel.Save’ is used to make an ajax call to server using WEB API to update the data. The method ‘EmpModel.resettemplate’ is used to reset the View.

Step 9: On the View, above the script added in the previous Task add the below HTML Templates:

<!--Templates-->
<!--View Template-->
<script type="text/html" id="viewtemplate">
<table border="1">
  <tr>
   <td>EmpNo</td>
   <td>
    <span data-bind="text: $data.EmpNo"></span>
   </td>
  </tr>
  <tr>
   <td>EmpName</td>
   <td>
    <span data-bind="text: $data.EmpName"></span>
   </td>
  </tr>
  <tr>
   <td>Salary</td>
   <td>
    <span data-bind="text: $data.Salary"></span>
   </td>
  </tr>
  <tr>
   <td>DeptName</td>
   <td>
    <span data-bind="text: $data.DeptName"></span>
   </td>
  </tr>
  <tr>
    <td>Designation</td>
    <td>
     <span data-bind="text: $data.Designation"></span>
    </td>
   </tr>
   <tr>
    <td>
     <button data-bind="click: function () { EmpModel.editRecordtemplate($data); }">Edit</button>
   </td>
  </tr>
</table>
</script>
<!--Ends Here-->

<!--Edit Template-->
<script id="edittemplate" type="text/html">
<table border="1" style="background-color:azure">
<tr>
  <td>EmpNo</td>
  <td>
   <input type="text" data-bind="value: $data.EmpNo" disabled="disabled"/>
  </td>
</tr>
<tr>
  <td>EmpName</td>
  <td>
   <input type="text" data-bind="value: $data.EmpName"/>
  </td>
</tr>
<tr>
  <td>Salary</td>
  <td>
   <input type="text" data-bind="value: $data.Salary"/>
  </td>
</tr>
<tr>
  <td>DeptName</td>
  <td>
   <input type="text" data-bind="value: $data.DeptName"/>
  </td>
</tr>
<tr>
  <td>Designation</td>
  <td>
   <input type="text" data-bind="value: $data.Designation"/>
  </td>
</tr>
<tr>
  <td>
   <input type="button" id="btnsave"  value="Save" data-bind="click: EmpModel.Save"/>
  </td>
  <td>
   <input type="button" id="btncancel"  value="Cancel" data-bind="click: function () { EmpModel.resettemplate(); }"/>
  </td>
</tr>
</table>
</script>
<!--Ends Here-->
<!--Ends Here-->


The above two templates are used for the following purpose:

viewtemplate: Used to display data on the Read-Only form. This template defines HTML table with <span> tags which are bound to with the EmployeeInfo properties e.g. EmpNo, EmpName etc. This contains an Edit button. This button’s ‘click’ event is bound to an anonymous methods that passes the current record ($data) to the EmpModel.editRecordtemplate.

edittemplate: Used to display data in Editable form. This template contains a table containing HTML textboxes which are bound to the EmployeeInfo properties. This template button which is bound to the ‘EmpModel.Save’ method. The Cancel button in this template is bind with the ‘EmpModel.resettemplate’.

Step 10: In the view, above the HTML template added in previous task add a HTML <div> tag as below:

<!--The HTML UI-->
<div id="dvemplist" data-bind="template: { name: templateDisplayed, foreach: Emps }"></div>


The above div tag is bind with the ‘templateDisplayed’ method defined in the EmpModel using ‘template’ binding. This method defines which template is displayed when the View is loaded. The template binding also uses ‘foreach’ parameter to the template binding. This parameter is passed with the ‘Emps’ declared in the EmpModel.

Step 11: In the App_Start folder in the RouteConfig class add the default route to the EmployeeInfo/ListData as below:

routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "EmployeeInfo", action = "ListData", id = UrlParameter.Optional }

When you run the application you should see the data as follows:

read-only-list

The above shows the view with the ‘viewtemplate’. Click on the ‘Edit’ button and the View will change to use the Edit template as we can see below:

one-item-editable

Now end-user can edit the record and click on save. The result will be as below (I have changed, the Designation from Team Lead to Team Lead (MS))

data-saved

Click on the ‘OK’, you will get the record Updated. If you click on Cancel button the page will be reloaded.

Conclusion

It’s fairly easy to build MVC applications using HTTP services over Web API, that use client side databinding and templating libraries or frameworks. There are lots of them today and Knockout JS as we saw is a relatively easy one to use for templating and data binding.

Download the entire source code of this article (Github)





About The Author

Mahesh Sabnis is a Microsoft MVP having over 18 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). He also blogs regularly at DotNetCurry.com. Follow him on twitter @maheshdotnet

3 comments:

Unknown said...

can't get this to run in my environment, VS Community on Windows 8.1 - same with other examples from Microsoft alumni people's demos, can't figure out the problem, i get the html but no data, so it is not crashing, just no data table output, did you not include the data??

luv your example otherwise
cb

Unknown said...

tanks for your article, i want to refresh my news content in my website( سئو سایت)from server to clien command. how to this task posible?
with best regards.

Saumya said...

Hi mahesh, thanks for this wonderful article. It helped me to start with MVC and Knockout.js
I have did the same steps and stuck. When I am running the application, I am getting the error as "Failed to load resource: net::ERR_CONNECTION_RESET http://localhost:1902/api/EmployeeInfoAPI" and when i clicked on this link of API i am getting "Cannot load this page"

Please help me to solve this, I am using Chrome