Introduction
In this article, I will discuss how to create a sample single page
application using Knockout and ASP.NET. This app shows a list of users based on types like
Users, Admin Users, Super Admin Users, and shows user details upon selection of
a user.
For building this app, we should have basic knowledge of Knockout, SAMMY library, WCF RESTful
services, and JavaScript.
Before we jump on to 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 displays 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 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 a WCF RESTful Service
- Create an ASP.NET website
- Define a ViewModel
- Define View
- Define Styles
- Output screen
Create a WCF RESTful Service
We will define two methods:
GetUsers
: returns a list of users based on usertypeGetUserData
: returns a list of users based on userid and usertype
These services would return a JSON object. The code is shown below. To know more about how to build
a RESTful service, find 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 my class of users, myCustomObject
.
Here is the definition:
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; }
}
Below is the implementation for the methods defined in the interface. I have avoided calling
the database as this is a sample, and I have 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 and 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 see the JSON object. Make sure that you don’t run your application using
port number.
Create an ASP.NET website
Create an ASP.NET website and add a new webpage named userslist.aspx.
By this time you must havce understood that KnockOut follows the MVVM pattern, i.e., Model, View, ViewModel pattern.
Add the below script reference to your page:
<script type="text/javascript" src="http://learn.knockoutjs.com/Scripts/CDNHosted/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="http://learn.knockoutjs.com/Scripts/Lib/knockout-2.1.0.js"></script>
<script src="http://learn.knockoutjs.com/scripts/lib/sammy.js" type="text/javascript"></script>
Define a ViewModel
Let's add the below script code to the page, see inline code comments:
<script type="text/javascript">
function UsersViewModel() {
var self = this;
self.userTypes = ['Users', 'AdminUsers', 'SuperAdminUsers'];
self.chosenuserTypeId = ko.observable();
self.chosenUserTypesData = ko.observable();
self.chosenUserData = ko.observable();
self.goToUserType = function (usertypeid) { location.hash = usertypeid };
self.goToUserData = function (userdata) { location.hash = userdata.usertypeid + '/' + userdata.userid };
Sammy(function () {
this.get('#:usertypeid', function () {
self.chosenuserTypeId(this.params.usertypeid);
self.chosenUserData(null);
$.get("http://localhost/wcfmailservice/ServiceOne.svc/GetUsers/Id/" +
this.params.usertypeid, self.chosenUserTypesData);
});
this.get('#:usertypeid/:userid', function () {
self.chosenuserTypeId(this.params.usertypeid);
self.chosenUserTypesData(null);
$.get("http://localhost/wcfmailservice/ServiceOne.svc/GetUserData/utypeid/" +
this.params.usertypeid + "/uid/" + this.params.userid, self.chosenUserData);
});
this.get('', function () { this.app.runRoute('get', '#Users') });
}).run();
};
ko.applyBindings(new UsersViewModel());
</script>
Define a View
<ul class="userTypes" data-bind="foreach: userTypes">
<li data-bind="text: $data,
css: { selected: $data == $root.chosenuserTypeId() },
click: $root.goToUserType"></li>
</ul>
<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>
<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>
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; }
.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; }
Output screen
Happy coding… Hope this helps!