In this article, I will discuss how to create a sample single-page application using KNOCKOUT & ASP.NET. This application shows a list of users based on types like Users, Admin Users, Super Admin Users & shows user details upon selection of a user.
For building this app, one should have basic knowledge of KNOCKOUT, the SAMMY library, WCF Restful Services and JavaScript.
Before we jump into the article let's have a look at the standard definition of KNOCKOUT.
Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model. Any time you have sections of UI that update dynamically (e.g., changing depending on the user's actions or when an external data source changes), KO can help you implement it more simply and maintainably.
Sammy.js is an excellent lightweight routing JavaScript library. You can do things like this to route when used in pair with Knockout (from the tutorials web site or KnockoutJS):
- Create WCF Restful Service
- Create ASP.NET web site
- Define ViewModel
- Define View
- Define Styles
- Output screen
1. Create WCF Restful Service
We will define two methods:
- GetUsers: returns a list of users based on usertype
- GetUserData: returns a list of users based on userid & usertype
These services would return a JSON object. Here is the code below. To know more about how to build a restful service, see my article at http://www.codeproject.com/Articles/275279/Developing-WCF-Restful-Services-with-GET-and-POST
I have defined my service as ServiceOne.svc, so my interface would we something like this:
[ServiceContract]
public interface IServiceOne
{
[OperationContract(Name = "GetUsers")]
[WebInvoke(Method = "GET", UriTemplate = "GetUsers/utypeid/{userTypeId}", RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
myCustomObject GetUsers(string userTypeId);
[OperationContract(Name = "GetUserData")]
[WebInvoke(Method = "GET", UriTemplate = "GetUserData/utypeid/{userTypeId}/uid/{userId}", RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
users GetUserData(string userTypeId, string userid);
}
Also, to work on this I define two classes, named myCustomObject and users.
Here is the definition for them:
public class myCustomObject
{
public string userTypeId { get; set; }
public List<users> users { get; set; }
}
public class users
{
public string userid { get; set; }
public string usertypeid { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
public string address { get; set; }
}
The following is the implementation for the methods defined in the interface. I have avoided use of a DB since this is a sample, so I have just defined static values.
public class ServiceOne : IServiceOne
{
public myCustomObject GetUsers(string userTypeId)
{
myCustomObject myObj = new myCustomObject();
myObj.userTypeId = userTypeId;
List<users> usersList = new List<users>();
if (userTypeId == "Users")
{
usersList.Add(new users() { usertypeid = "Users", userid = "1", firstname = "user fname 1", lastname = "user lname 1", address = "user address 1" });
usersList.Add(new users() { usertypeid = "Users", userid = "2", firstname = "user fname 2", lastname = "user lname 2", address = "user address 2" });
}
else if (userTypeId == "AdminUsers")
{
usersList.Add(new users() { usertypeid = "AdminUsers", userid = "3", firstname = "admin user fname 1", lastname = "admin user lname 1", address = "admin user address 1" });
usersList.Add(new users() { usertypeid = "AdminUsers", userid = "4", firstname = "admin user fname 2", lastname = "admin user lname 2", address = "admin user address 2" });
}
else if (userTypeId == "SuperAdminUsers")
{
usersList.Add(new users() { usertypeid = "SuperAdminUsers", userid = "5", firstname = "super admin user fname 1", lastname = "super admin user lname 1", address = "super admin user address 1" });
usersList.Add(new users() { usertypeid = "SuperAdminUsers", userid = "6", firstname = "super admin user fname 2", lastname = "super admin user lname 2", address = "super admin user address 2" });
}
myObj.users = usersList;
return myObj;
}
public users GetUserData(string userTypeId, string userid)
{
List<users> usersList = new List<users>();
usersList.Add(new users() { usertypeid = "Users", userid = "1", firstname = "user fname 1", lastname = "user lname 1", address = "user address 1" });
usersList.Add(new users() { usertypeid = "Users", userid = "2", firstname = "user fname 2", lastname = "user lname 2", address = "user address 2" });
usersList.Add(new users() { usertypeid = "AdminUsers", userid = "3", firstname = "admin user fname 1", lastname = "admin user lname 1", address = "admin user address 1" });
usersList.Add(new users() { usertypeid = "AdminUsers", userid = "4", firstname = "admin user fname 2", lastname = "admin user lname 2", address = "admin user address 2" });
usersList.Add(new users() { usertypeid = "SuperAdminUsers", userid = "5", firstname = "super admin user fname 1", lastname = "super admin user lname 1", address = "super admin user address 1" });
usersList.Add(new users() { usertypeid = "SuperAdminUsers", userid = "6", firstname = "super admin user fname 2", lastname = "super admin user lname 2", address = "super admin user address 2" });
users obj = (from user in usersList.Where(val => val.userid == userid && val.usertypeid == userTypeId)
select user).First();
return obj;
}
}
Host your service in IIS & test it by using the URL; in my case it is as follows:
http://localhost/wcfrestservice/ServiceOne.svc/GetUsers/id/Users
By clicking it, you should be able to JSON object. Make sure that you don't run your application using Port No.
2. Create ASP.NET web site
Create an ASP.Net web site & add a new webpage name, userslist.aspx.
By this time you must understand that KnockOut follows a MVVM pattern i.e. Model, View, ViewModel pattern.
a. Add the following script reference to your page:
<!-- JQUERY Library -->
<script type="text/javascript" src="http://learn.knockoutjs.com/Scripts/CDNHosted/jquery-1.7.1.min.js"></script>
<!-- KNOCKOUT Library -->
<script type="text/javascript" src="http://learn.knockoutjs.com/Scripts/Lib/knockout-2.1.0.js"></script>
<!-- SAMMY Library -->
<script src="http://learn.knockoutjs.com/scripts/lib/sammy.js" type="text/javascript"></script>
3. Define ViewModel
Let's add the following script code to the page; see the inline code comments:
<script type="text/javascript">
function UsersViewModel() {
// Data
var self = this;
// defines usertypes
self.userTypes = ['Users', 'AdminUsers', 'SuperAdminUsers'];
// holds usertypeid, when usertype is selected
self.chosenuserTypeId = ko.observable();
// holds list of users for a selected usertype
self.chosenUserTypesData = ko.observable();
// hold userdata for a selected user
self.chosenUserData = ko.observable();
// Behaviours
self.goToUserType = function (usertypeid) { location.hash = usertypeid };
self.goToUserData = function (userdata) { location.hash = userdata.usertypeid + '/' + userdata.userid };
// Client-side routes, can be defined by using SAMMY library
Sammy(function () {
// below segment is used to fetch userslist based on usertypeid
this.get('#:usertypeid', function () {
// sets selected usertypeid
self.chosenuserTypeId(this.params.usertypeid);
// destroy userdata, because at this point we need to have userslist instead of userdata
self.chosenUserData(null);
// make a call to wcf restful service, to fetchs users based on usertypeid
// response from service would contains "userslist", which is filled into chosenUserTypesData, whenever we statement "self.chosenUserTypesData"
$.get("http://localhost/wcfmailservice/ServiceOne.svc/GetUsers/Id/" + this.params.usertypeid, self.chosenUserTypesData);
});
// below segment is used to fetch userdata based on usertypeid & userid
this.get('#:usertypeid/:userid', function () {
// sets selected usertypeid
self.chosenuserTypeId(this.params.usertypeid);
// destroys userslist, because at this point we need to have userdata instead of userslist
self.chosenUserTypesData(null);
// make a call to wcf service, to fetch user data based on usertypeid & userid
$.get("http://localhost/wcfmailservice/ServiceOne.svc/GetUserData/utypeid/" + this.params.usertypeid + "/uid/" + this.params.userid, self.chosenUserData);
});
// when page load happens show default data, using sammy routing mechanism
this.get('', function () { this.app.runRoute('get', '#Users') });
}).run();
};
// allows to bind viewmodel defined above
ko.applyBindings(new UsersViewModel());
</script>
4. Define View
<!-- Users Types, to show list of usertypes based on usertype defined -->
<!-- foreach allows to iterate through the usertypes declared -->
<ul class="userTypes" data-bind="foreach: userTypes">
<li data-bind="text: $data,
css: { selected: $data == $root.chosenuserTypeId() },
click: $root.goToUserType"></li>
</ul>
<!-- Users Grid: with keyword allows below section to be shown only when data in chosenUserTypesData is available -->
<table class="userslist" data-bind="with: chosenUserTypesData">
<thead><tr><th>First Name</th><th>Last Name</th><th>Address</th></tr></thead>
<tbody data-bind="foreach: users">
<tr data-bind="click: $root.goToUserData">
<td data-bind="text: firstname"></td>
<td data-bind="text: lastname"></td>
<td data-bind="text: address"></td>
</tr>
</tbody>
</table>
<!-- Selected User: with key allows to below section to be shown only when chosenUserData is available-->
<div class="userData" data-bind="with: chosenUserData">
<div class="userdetails">
<p><label>First Name</label>: <span data-bind="text: firstname"></span></p>
<p><label>Last Name</label>: <span data-bind="text: lastname"></span></p>
</div>
</div>
5. Define styles
.userTypes { background-color: #bbb; list-style-type: none; padding: 0; margin: 0; border-radius: 7px;
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #d6d6d6), color-stop(0.4, #c0c0c0), color-stop(1,#a4a4a4));
margin: 10px 0 16px 0;
font-size: 0px;
}
.userTypes li:hover { background-color: #ddd; }
.userTypes li:first-child { border-left: none; border-radius: 7px 0 0 7px; }
.userTypes li { font-size: 16px; font-weight: bold; display: inline-block; padding: 0.5em 1.5em; cursor: pointer; color: #444; text-shadow: #f7f7f7 0 1px 1px; border-left: 1px solid #ddd; border-right: 1px solid #888; }
.userTypes li { *display: inline !important; } /* IE7 only */
.userTypes .selected { background-color: #444 !important; color: white; text-shadow:none; border-right-color: #aaa; border-left: none; box-shadow:inset 1px 2px 6px #070707; }
.userslist { width: 100%; table-layout:fixed; border-spacing: 0; }
.userslist thead { background-color: #bbb; font-weight: bold; color: #444; text-shadow: #f7f7f7 0 1px 1px; }
.userslist tbody tr:hover { cursor: pointer; background-color: #68c !important; color: White; }
.userslist th, .userslist td { text-align:left; padding: 0.4em 0.3em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.userslist th { border-left: 1px solid #ddd; border-right: 1px solid #888; padding: 0.4em 0 0.3em 0.7em; }
.userslist th:nth-child(1), .userslist td:nth-child(1) { width: 20%; }
.userslist th:nth-child(2), .userslist td:nth-child(2) { width: 15%; }
.userslist th:nth-child(3), .userslist td:nth-child(3) { width: 45%; }
.userslist th:nth-child(4), .userslist td:nth-child(4) { width: 15%; }
.userslist th:last-child { border-right: none }
.userslist tr:nth-child(even) { background-color: #EEE; }
.userData .userdetails { background-color: #dae0e8; padding: 1em 1em 0.5em 1.25em; border-radius: 1em; }
.userData .userdetails h1 { margin-top: 0.2em; font-size: 130%; }
.userData .userdetails label { color: #777; font-weight: bold; min-width: 2.75em; text-align:right; display: inline-block; }
.userData .message { padding: 0 1.25em; }
6. Output screen

Happy Coding.

Santhakumar MunuswamyPosted May 23, 2015, 3:50 PM
Thank for good work
Gowtham RajamanickamPosted Apr 25, 2015, 5:57 AM
good
Neeti JainPosted Oct 5, 2012, 5:32 AM
Got the solution. The entire script needs to be placed towards the end of the page asthe scriptgets executed before the page fully loads causing it to fail. Thanks Neeti
Neeti JainPosted Oct 3, 2012, 5:15 AM
Sorry for typo :( Hi Surya, Andres, I'm also facing the same issue as Andres while running the application in knockout JS. I get following error: Microsoft JScript Runtime error: Unable to get value of the property 'nodeType': object is null or undefined. My service runs fine in browser. I also checked in Fiddler, wcf is returning JSON to web site. But somehow things are not working. I've hosted both applications and service under localhost (default web site) as separate applications. Please suggest what could be going wrong.
Neeti JainPosted Oct 3, 2012, 5:12 AM
Hi Surya, Andres, I'm also facing the same issue as Andres while running the application in knockout JS. I get following error: Microsoft JScript Runtime error: Unable to get
Neeti JainPosted Oct 3, 2012, 5:12 AM
Hi Surya, Andres, I'm also facing the same issue as Andres while running the application in knockout JS. I get following error: Microsoft JScript Runtime error: Unable to get
Neeti JainPosted Oct 3, 2012, 5:11 AM
z
Surya PrakashPosted Jun 28, 2012, 3:55 AM
Andres, as stated by you both are hosted under single domain. I would ask you to check the service & web application individually by looking at HTTPFOX. I mean to check requests made to service from application & requests made manually in HTTPFOX (check service call). Check the response content in HTTPFOX for both the cases.I hope this should help you.
Andres RomeroPosted Jun 27, 2012, 2:42 PM
Surya, i only have the following error since i´ve published both proyects on my local iis: Uncaught TypeError: Cannot read property 'nodeType' of null knockout-2.1.0.js:49 with this error the form doesnt recieve the json data.
Andres RomeroPosted Jun 27, 2012, 2:31 PM
Hi Surya, i have the following errors Uncaught TypeError: Cannot read property 'nodeType' of null knockout-2.1.0.js:49 this error comes from knockout...what could it be?? XMLHttpRequest cannot load http://localhost/WCFRestService/ServiceOne.svc/GetUsers/utypeid/Users. Origin http://localhost:3781 is not allowed by Access-Control-Allow-Origin. i guess this last one is because i'm executing the client from the integrate web server of VS2010 and the WCF service is hosted on my local IIS....is it? I cought teese error with the chrome debugger. Is there a chance that i can send you my project so you can see if there is any error??
Andres RomeroPosted Jun 27, 2012, 9:28 AM
Surya, thanks for your reply. It was my fault! I configured the web.config in a wrong way. I'm doing the rest of the example. If i have any trouble i'll let you know (sorry for my english!!)
Surya PrakashPosted Jun 27, 2012, 9:18 AM
Hi Andres, I suspect the problem could be with your WCF service. To check whether your service itself is working properly or not. To test it call service as http://localhost:1944/ServiceOne.svc/GetUsers/id/Users & you should be able to see JSON object. If this is not happening please do let me know what details of the error. So that I can help you in this regard.
Andres RomeroPosted Jun 26, 2012, 11:24 AM
Hi Surya. I've tried the code but the WCF service doesn´t return to me any json. Can you help me??