Introduction
Knockout JS
Knockout JS is a data binding JavaScript library. It is a well know library which is being used to make rich, responsive UI and underlying data-model. The beauty of the use of this library is that the respective UI part gets updated whenever the data model changes. No need to write any additional code to trace data model or UI changes when you are using knockout JS.
Web API OData Controller
OData is an Open standard protocol which gives us capabilities to construct and consume queryable restful API. In other words, we can create URI to get specific details and consume this result as per our requirement.
Such as for getting the top 10 product list, we can form a URL:
The good thing is that formed query will execute on the database directly, hence there is no direct performance impact on the application and we can even create any complex type query.
Now a days, most of the applications are using knockout for client side data model and web API for further communication or service layer. This means client side data model is directly communicating to the service layer.
Keeping the above point in my mind, here I am trying to put my effort to implement full flow of the application by using knockout as a data binding with front end, Web API Odata controller as a service layer and MVC5 for frontend layer.
Background
When I was learning MVC5.0, knockout (client side data-binding), web API odata controller. I was searching for a simple demo, which can explain the whole flow of application from frontend to data access by using all these technologies, but fortunately or unfortunately I did not get any simplified example. So I ended up spending some time, came up with this simple demo.
The main purpose of this demo is as follows:
- Fetch the data from Data Access layer to service layer using Web API OData Controller
- WebAPI configuration from config.cs file
- Access web API method from client side data model
- Bind client side data model to HTML, using knockout
- Interact with HTML control to client side data model
Using the Code
Let’s go with a very simple example:
Step 1
I created two class library projects; first for Entities and second for data access.
In the entities project, I have introduced product
as an entity with some properties.
public class Product
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public double Price { get; set; }
public string Category { get; set; }
}
In the data access layer, I have introduced a folder called Repository and inside these folders, have created an interface and class IProductRepository
and ProductRepository
respectively.
public interface IProductRepository
{
List<Product> GetProducts();
Product GetProductById(int id);
}
Here, I am not going to use any specific data access concept, so for this demo, I am making hard coded values.
Public class ProductRepository
{
public List<Product> GetProducts()
{
List<Product> products = new List<Product>();
produts.Add(new Product { Id = 1, Title = "Product1",
Description = "Test Product", ImageUrl = "", Price = 1.01, Category = "P1" });
produts.Add(new Product { Id = 2, Title = "Product2",
Description = "Test Product", ImageUrl = "", Price = 2.01, Category = "P2" });
produts.Add(new Product { Id = 3, Title = "Product3",
Description = "Test Product", ImageUrl = "", Price = 3.01, Category = "P3" });
produts.Add(new Product { Id = 4, Title = "Product4",
Description = "Test Product", ImageUrl = "", Price = 4.01, Category = "P4" });
produts.Add(new Product { Id = 5, Title = "Product5",
Description = "Test Product", ImageUrl = "", Price = 5.01, Category = "P1" });
return produts;
}
public Product GetProductById(int id)
{
var listProducts = GetProducts();
return listProducts.Where(i => i.Id == id).Select
(p => new Product { Id = p.Id, Title = p.Title,
Description = p.Description, Category = p.Category }).FirstOrDefault();
}
}
As of now, my solution looks like below:
Step 2
Now we are going to create the WebApi project.
Add new project and select ASP.NET Web Application template.
Click on OK and select WebAPI template.
Deleted the un-necessary files.
Add ASP.NET Odata reference to webApi project.
Added a ProductController.cs file inside the controller folder. Now my solution appears the same as below:
In the ProductController
calls, I am inheriting ODataController
class. I have commented HttpGet
and EnableQuery
attribute from the top of method name, because we are not going to use this feature for this time.
public class ProductController : ODataController
{
ProductRepository _productRepository;
public ProductController()
{
_productRepository = new ProductRepository();
}
[ODataRoute("GetProducts1()")]
public String GetProducts()
{
return JsonConvert.SerializeObject(_productRepository.GetProducts());
}
public String GetProductsById(int id)
{
return JsonConvert.SerializeObject(_productRepository.GetProductById(id));
}
}
Here on ODataRoute
, I have written GetProducts1
; to make a difference between ODataRoute
and method name, which I’ll explain further down.
Now one of the import sections, webApi
config.
In this file, we use to define functions and their configurations such as their input parameter, return type.
Map the entities, functions to the model and one of the most important thing routing.
Here, in this example, I have made route name and prefix fix, so that a uniform request could come from the front end.
public static void Register(HttpConfiguration config)
{
ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Product>("Products");
var getProductConfig = modelBuilder.Function("GetProducts1");
getProductConfig.Returns<String>();
var getProductByIdConfig = modelBuilder.Function("GetProductById");
getProductByIdConfig.Parameter<int>("Id");
getProductByIdConfig.Returns<String>();
config.MapODataServiceRoute(routeName: "ODataRoute",
routePrefix: "ProductOData", model: modelBuilder.GetEdmModel());
}
Here, you can see that model builder is mapping to the oDataRoute
not the function name (GetProducts1
not GetProducts
), hence function configuration depends upon data route not the function.
Step 3
It’s time to add frontend website.
I have chosen MVC template for this demo.
Deleted the un-necessary files.
I have added an MVC controller name product Controller
.
Added one view: Index view
partial view: _productList
Now my solution appear as:
For the client side model, creating menu.js and productlist.js inside script folder which will be bind with menu and _productlist
view respectively.
Export knockoutJS component from nugget packager manager.
Add knockout reference to BundleConfig.cs file.
Set the routing with product controller in router config.
For this demo, set project URL the same for web API and web app, so that as soon as you will run the app, parallel service will also run and give the output.
For web App:
For API:
On product.js, we are going to call this web API method and push it to observable Array
. Now this observable array will bind to the HTML page.
var productListViewModel = function () {
var self = this;
self._listProduct = [];
self.Products = ko.observableArray([]);
self.getProducts = function () {
var productDataUrl = "Api/ProductOData/GetProducts1";
$.ajax({
url: productDataUrl,
type: "GET",
success: function (result) {
self._listProduct = $.parseJSON(result.value);
self.Products(self._listProduct);
},
error: function (data) {
alert(data);
}
})
}
};
var vm = new productListViewModel();
vm.getProducts();
ko.applyBindings(vm, $("#divList")[0]);
First, we will traverse the array with foreach
loop and then bind each element to the respective control.
<table class="table table-striped table-bordered table-condensed">
<tr>
<th>Title</th>
<th>Description</th>
<th>Image</th>
<th>Price</th>
<th>Category</th>
</tr>
<tbody data-bind="foreach:Products">
<tr>
<td data-bind="text:Title"></td>
<td data-bind="text:Description"></td>
<td data-bind="text:ImageUrl"></td>
<td data-bind="text:Price"></td>
<td data-bind="text:Category"></td>
<td><button class="btn btn-mini btn-danger"
data-bind="click: $parent.removeProfile">Remove</button></td>
<td><button class="btn btn-mini btn-danger"
data-bind="click: $parent.editProfile">Edit</button></td>
</tr>
</tbody>
</table>
Points of Interest
In this article, we have discussed about the whole flow of the application such as:
- Data flow from data access layer to front
- Data flow from data access layer to service layer
- Web API OData controller
- Web API configurations with functions\ routing
- Data flow from Service layer to client side data model
- Knockout binding with server side to client side data model and client side data model to HTML controls
I am writing this article keeping beginners in my mind. In my next article, I’ll cover more on OData controller concepts.
Hope this article will be helpful for you.
Source Code
I have uploaded this sample project with this article. That is MVC project with ASP.NET 4.5 version with Visual Studio 2015. You can just download this sample code and make your understanding better on these concepts and implementations.
History
- 16th December, 2005: Initial version