Introduction
I will demonstrate how we can create sample CRUD (Create, Read, Update, Delete) operations using ASP.NET, Web API, and Knockout.js. I hope you will like this.
Prerequisites
First, you must have Visual Studio 2015 (.NET Framework 4.5.2) and a SQL Server.
In this post, we are going to:
1. Create an MVC application.
2. Configure an Entity framework ORM to connect to a database.
3. Implementing all the HTTP Services needed.
4. Call Services using Knockout.js.
SQL Database Part
Here, you will find the scripts to create your database and table.
Create Table
CREATE TABLE [dbo].[User](
[UserID] [int] IDENTITY(1,1) NOT NULL,
[Fname] [varchar](50) NULL,
[Lname] [varchar](50) NULL) ON [PRIMARY]
Create Your MVC application
Open Visual Studio and select File >> New Project.
The “New Project” window will pop up. Select ASP.NET Web Application (.NET Framework), name your project, and click OK.
Next, a new window will pop up for selecting the template. We are going to choose Web API template and click OK.
After creating our project, we are going to add the ADO.NET Entity Data model.
Adding ADO.NET Entity Data Model
To add the ADO.NET Entity Framework, right click on the project name, click Add > Add New Item. A dialog box will pop up. Inside this box, select Visual C# Data then ADO.NET Entity Data Model, and enter a name for your Dbcontext model, such as Customer Model, and finally click Add.
Next, we need to choose model contain for the EF Designer from the database.
As you can see below, we need to select a server name, then, via a drop down list, connect it to a database panel. You should choose your database name. Finally, click OK.
Now, the Entity Data Model Wizard window will pop up for choosing an object which we need to use. In our case, we are going to choose Customers table and click Finish. Finally, we see that the EDMX model generates a Customer class.
Create a Controller
Now, we are going to create a controller. Right click on the controller’s folder > Add > Controller> selecting Web API 2 Controller with actions using Entity Framework > click Add.
In the snapshot given below, we are providing three important parameters:
1. Model class: Customer represents the entity that should be used for CRUD operations.
2. Data context class: used to establish a connection with the database.
3. Finally, we need to name our controller (in this case Customers Controller).
As we already know, Web API is a framework that makes it easy to build HTTP services that reach a broad range of clients including browsers and mobile devices.
It has four methods:
1. Get is used to select data.
2. Post is used to create or insert data.
3. Put is used to update data.
4. Delete is used to delete data.
UsersController.cs
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using UserAPI.Models;
namespace UserAPI.Controllers
{
public class UsersController : ApiController
{
private RegistrationEntities db = new RegistrationEntities();
// GET: api/Users
public List GetUsers()
{
dynamic user = db.Users.ToList();
List userList = new List();
foreach (var name in user)
{
UserDTO dto = new UserDTO();
dto.UserId = name.UserId;
dto.Fname = name.Fname;
dto.Lname = name.Lname;
userList.Add(dto);
}
return userList;
}
// GET: api/Users/5
[ResponseType(typeof(UserDTO))]
public async Task GetUser(int id)
{
//var user = db.Users.Where(u => u.UserId == id).ToList().SingleOrDefault(u => u.UserId == id);
var user = db.Users.Where(u => u.UserId == id).FirstOrDefaultAsync();
if (user == null)
{
var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format("No User with Id={0}", id)),
ReasonPhrase = "User ID not found"
};
throw new HttpResponseException(resp);
}
return Ok(user);
}
// PUT: api/Users/5
[ResponseType(typeof(void))]
public async Task PutUser(int id, User user)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != user.UserId)
{
return BadRequest();
}
db.Entry(user).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!UserExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
// POST: api/Users
[ResponseType(typeof(UserDTO))]
public async Task PostUser(User user)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Users.Add(user);
await db.SaveChangesAsync();
var dto = new UserDTO()
{
UserId = user.UserId,
Fname = user.Fname,
Lname = user.Lname
};
return CreatedAtRoute("DefaultApi", new { id = user.UserId }, dto);
}
// DELETE: api/Users/5
[ResponseType(typeof(UserDTO))]
public async Task DeleteUser(int id)
{
User = await db.Users.FindAsync(id);
if (user == null)
{
return NotFound();
}
db.Users.Remove(user);
await db.SaveChangesAsync();
var dto = new UserDTO()
{
UserId = user.UserId,
Fname = user.Fname,
Lname = user.Lname
};
return Ok(dto);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool UserExists(int id)
{
return db.Users.Count(e => e.UserId == id) > 0;
}
}
}
Calling Services Using Knockout.js
First of all, we need to install Knockout.js. From the solution explorer panel, right click on references > Manage NuGet Packages…
Next, type Knockout.js in the search text box, select the first line as below and click Install.
Now, we need to add a new JS file. Right click on scripts folder > Add > JavaScript File.
App.js
Here, we create our view model that contains all the business logic. Then, we bind it with ko.applyBindings(new viewModel()) which enables us to activate Knockout for the current HTML document.
As you can see in the below code, KO provides observables to bind to the model.
1. Ko.observable(): used to define model properties which can notify the changes and update the model automatically.
2. Ko.observableArray([]): used to bind list of elements.
var ViewModel = function () {
var self = this;
self.UserId = ko.observable();
self.Fname = ko.observable();
self.Lname = ko.observable();
self.userList = ko.observableArray([]);
self.Mutated = ko.observableArray([]);
var UserUri = '/api/Users/';
function ajaxFunction(uri, method, data) {
return $.ajax({
type: method,
url: uri,
dataType: 'json',
contentType: 'application/json',
data: data ? JSON.stringify(data) : null
}).fail(function (jqXHR, textStatus, errorThrown) {
alert('Error: ' + errorThrown);
});
}
self.clearFields = function () {
self.Mutated.removeAll();
}
self.addMoreUser = function () {
var UserObject = {
UserId: self.UserId(),
Fname: self.Fname(),
Lname: self.Lname()
};
self.Mutated.push(UserObject);
};
self.removeUser = function (user) {
self.Mutated.remove(user);
};
self.saveAll = function saveAll() {
for (var i = 0; i < self.Mutated().length; i++) {
var UserObject1 = {
UserId: self.Mutated()[i].UserId,
Fname: self.Mutated()[i].Fname,
Lname: self.Mutated()[i].Lname
};
ajaxFunction(UserUri, 'POST', UserObject1).done(
function () {
if (i == self.Mutated().length - 1) {
}
}
);
}
self.clearFields();
alert('All Records added successfully!');
getuserList();
};
self.saveUser = function saveUser() {
var UserObject = {
UserId: self.UserId(),
Fname: self.Fname(),
Lname: self.Lname()
};
ajaxFunction(UserUri, 'POST', UserObject).done(function () {
self.clearFields();
alert('User added successfully!');
getuserList()
});
}
function getuserList() {
$("div.loadingZone").show();
ajaxFunction(UserUri, 'GET').done(function (data) {
$("div.loadingZone").hide();
self.userList(data);
});
}
self.detailUser = function (selectedUser) {
var UserObject = {
UserId: selectedUser.UserId,
Fname: selectedUser.Fname,
Lname: selectedUser.Lname
};
self.Mutated.push(UserObject);
$('#Save').hide();
$('#Clear').hide();
$('#Add').hide();
$('#SaveAll').hide();
$('#Update').show();
$('#Delete').show();
$('#Cancel').show();
};
self.cancel = function () {
self.clearFields();
$('#Save').hide();
$('#Clear').show();
$('#Add').show();
$('#SaveAll').show();
$('#Update').hide();
$('#Delete').hide();
$('#Cancel').hide();
}
self.updateUser = function () {
for (var i = 0; i < self.Mutated().length; i++) {
var UserObject = {
UserId: self.Mutated()[i].UserId,
Fname: self.Mutated()[i].Fname,
Lname: self.Mutated()[i].Lname
};
ajaxFunction(UserUri + self.Mutated()[i].UserId, 'PUT', UserObject).done(function () {
if (i == self.Mutated().length - 1) { }
});
}
alert('Some Users updated successfully!');
getuserList();
self.cancel();
}
self.deleteUser = function () {
for (var i = 0; i < self.Mutated().length; i++) {
ajaxFunction(UserUri + self.Mutated()[i].UserId, 'DELETE').done(function () {
if (i == self.Mutated().length - 1) { }
})
}
alert('Some Users deleted successfully!');
getuserList();
self.cancel();
}
getuserList();
};
ko.options.deferUpdates = true;
ko.applyBindings(new ViewModel());
In order to exchange data between an HTML page and JavaScript file, Knockout.js offers various types of binding that should be used within the data-bind attribute.
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/bootstrap.min.js"></script>
<script src="~/Scripts/knockout-3.5.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
<!-- app.js-->
@section scripts {
@Scripts.Render("~/bundles/app")
}
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">User API</a>
</div>
</div>
</nav>
<div class="container" style="margin-top:7%;">
<div class="row">
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-tag" aria-hidden="true"></span>
<b>Add New User</b>
</div>
<div class="panel-body">
<form>
<div class="form-group">
<table data-bind="visible:Mutated().length>0">
<thead>
<tr>
<th style="display:none;"><label for="inputUserId">User ID</label></th>
<th><label for="inputFirstName">First Name</label></th>
<th><label for="inputLastName">Last Name</label></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach:Mutated">
<tr>
<td style="display:none;"><input type="text" id="inputUserId" class="form-control" data-bind="value:UserId" placeholder="User ID" /></td>
<td><input type="text" id="inputFirstName" class="form-control" data-bind="value:Fname" placeholder="First Name" /></td>
<td><input type="text" id="inputLastName" class="form-control" data-bind="value:Lname" placeholder="Last Name" /></td>
<td>
<button type="button" class="btn btn-danger btn-xs" data-bind="click:$root.removeUser">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<button type="button" class="btn btn-default" data-bind="click:addMoreUser" id="Add">
<span class="glyphicon glyphicon glyphicon-edit" aria-hidden="true"></span>
Add
</button>
<button type="button" class="btn btn-success" data-bind="click:saveAll" id="SaveAll">
<span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
Save All
</button>
<button type="button" class="btn btn-success" data-bind="click:saveUser" style="display:none;" id="Save">
<span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
Save
</button>
<button type="button" class="btn btn-info" data-bind="click:clearFields" id="Clear">
<span class="glyphicon glyphicon-refresh" aria-hidden="true"></span>
Clear
</button>
<button type="button" class="btn btn-warning" data-bind="click:updateUser" style="display:none;" id="Update">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
Update
</button>
<button type="button" class="btn btn-danger" data-bind="click:deleteUser" style="display:none;" id="Delete">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
Delete
</button>
<button type="button" class="btn btn-default" data-bind="click:cancel" style="display:none;" id="Cancel">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
Cancel
</button>
</form>
</div>
</div>
</div>
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span>
<b>User List</b>
<div class="loadingData" style="color:#000; display:block; float:right; display:none;">
Refresh Data...
</div>
</div>
<div class="panel-body">
<table class="table table-hover">
<thead>
<tr>
<th style="display:none;">#</th>
<th>First Name</th>
<th>Last Name</th>
<th><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>Edit</th>
<th><span class="glyphicon glyphicon-trash" aria-hidden="true"></span>Delete</th>
</tr>
</thead>
<tbody data-bind="foreach:userList">
<tr>
<td style="display:none;">
<span data-bind="text:UserId"></span>
</td>
<td>
<span data-bind="text:Fname"></span>
</td>
<td>
<span data-bind="text:Lname"></span>
</td>
<td>
<button type="button" class="btn btn-default btn-xs" data-bind="click:$root.detailUser">
<span class="glyphicon glyphicon glyphicon-pencil" aria-hidden="true"></span>
</button>
</td>
<td>
<button type="button" class="btn btn-danger btn-xs" data-bind="click:$root.deleteUser">
<span class="glyphicon glyphicon glyphicon-trash" aria-hidden="true"></span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
Click: represents a click event handler to call a JavaScript function.
Value: represents the value binding with UI elements to the property defined into view Model.
Text: represents the text value to the UI element.
For each: used to fetch an array.
Now you can run your application. Don’t forget to change the URL address as below.
http://localhost:2337/Home/Index
Let’s see the output