A Single Page Application (SPA) is a web application that fits on a single web page. In this type of application, the server provides static HTML views, CSS and JavaScript and the application then loads data by making Ajax calls to the server. All subsequent views and navigation occurs without a postback to the server. This architecture provides a more fluid user experience just like a desktop application.
In this article, we will create a SPA using ASP.NET MVC, WEB API and Angular.js. Angular.js is a Model-View-* JavaScript based framework for developing SPA applications. Similarly ASP.NET Web API is a good fit for providing data for these type of applications.
To create this application, you can use the free new Visual Studio Community 2013 (http://www.visualstudio.com/products/visual-studio-community-vs) Edition which is a full-featured IDE.
Step 1: Open Visual Studio 2013 and create a new Empty MVC application. Name it as ‘MVC_Using_Angular’. In this MVC project, add a new Sql Server database with the name ‘Company.mdf’. In the database add a new EmployeeInfo table with the following script:
CREATE TABLE [dbo].[EmployeeInfo] (
[EmpNo] INT IDENTITY (1, 1) NOT NULL,
[EmpName] VARCHAR (50) NOT NULL,
[Salary] DECIMAL (18) NOT NULL,
[DeptName] VARCHAR (50) NOT NULL,
[Designation] VARCHAR (50) NOT NULL,
PRIMARY KEY CLUSTERED ([EmpNo] ASC)
);
Step 2: In the project, add Angular.js scripts using NuGet Package Manager. This will create a new ‘Scripts’ folder in the project, with Angular.js scripts in it.
Step 3: To create a model, right-click on the Models folder and add a new ADO.NET Entity Model with the name CompanyEDMX. Select the Database First approach. Select Company.mdf in the Wizard and then the EmployeeInfo table. After completion of the Wizard, the mapping will look like this:
Step 4: In the Controllers folder, add a new WEB API 2 Controller with actions. Using Entity Framework, select the Model as EmployeeInfo and Context class as CompanyEntities. Name the API controller as EmployeeInfoAPIController. This will generate methods for performing GET, POST, PUT and DELETE operations.
Step 5: In the Controllers folder, add an empty MVC controller of the name EmployeeInfo Controller. This controller will have an ‘Index’ action method in it. To add a new Index View, right-click on the Index action method and add an Empty View without Model. In our SPA, we will use the Index.cshtml as a container for dynamically displaying all views. In the EmployeeInfo sub-folder of the Views folder, add an empty partial views for performing various operations. These views will have the following names:
- ShowEmployees.cshtml - show all Employees.
- AddEmployee.cshtml - allow to add new Employee.
- EditEmployee.cshtml - allow to edit an Employee.
- DeleteEmployee.cshtml - used to delete selected Employee.
Step 6: Open EmployeeInfo Controller and add the following Action Methods in it:
public ActionResult AddNewEmployee()
{
return PartialView("AddEmployee");
}
public ActionResult ShowEmployees()
{
return PartialView("ShowEmployees");
}
public ActionResult EditEmployee()
{
return PartialView("EditEmployee");
}
public ActionResult DeleteEmployee()
{
return PartialView("DeleteEmployee");
}
The above action methods returns Partial Views added in the Step 5.
Step 7: Now its’ time for us to add the necessary client-side logic for performing operations using WEB API. In the project, add a new folder of the name ‘MyScripts’. In this folder add a new JavaScript file of name ‘Module.js’.
var app = angular.module("ApplicationModule", ["ngRoute"]);
//The Factory used to define the value to
//Communicate and pass data across controllers
app.factory("ShareData", function () {
return { value: 0 }
});
//Defining Routing
app.config(['$routeProvider','$locationProvider', function ($routeProvider,$locationProvider) {
$routeProvider.when('/showemployees',
{
templateUrl: 'EmployeeInfo/ShowEmployees',
controller: 'ShowEmployeesController'
});
$routeProvider.when('/addemployee',
{
templateUrl: 'EmployeeInfo/AddNewEmployee',
controller: 'AddEmployeeController'
});
$routeProvider.when("/editemployee",
{
templateUrl: 'EmployeeInfo/EditEmployee',
controller: 'EditEmployeeController'
});
$routeProvider.when('/deleteemployee',
{
templateUrl: 'EmployeeInfo/DeleteEmployee',
controller: 'DeleteEmployeeController'
});
$routeProvider.otherwise(
{
redirectTo: '/'
});
// $locationProvider.html5Mode(true);
$locationProvider.html5Mode(true).hashPrefix('!')
}]);
The above code defines module of name ‘ApplicationModule’. The module defines factory of name ‘ShareData’, which is used to communicate and pass data across controllers. The module also defines routing expressions using $routeProvider.
Step 8: In this step, we will add an Angular Service to encapsulate call to WEB API for performing CRUD operations. The code is as follows:
app.service("SinglePageCRUDService", function ($http) {
//Function to Read All Employees
this.getEmployees = function () {
return $http.get("/api/EmployeeInfoAPI");
};
//Fundction to Read Employee based upon id
this.getEmployee = function (id) {
return $http.get("/api/EmployeeInfoAPI/" + id);
};
//Function to create new Employee
this.post = function (Employee) {
var request = $http({
method: "post",
url: "/api/EmployeeInfoAPI",
data: Employee
});
return request;
};
//Function to Edit Employee based upon id
this.put = function (id,Employee) {
var request = $http({
method: "put",
url: "/api/EmployeeInfoAPI/" + id,
data: Employee
});
return request;
};
//Function to Delete Employee based upon id
this.delete = function (id) {
var request = $http({
method: "delete",
url: "/api/EmployeeInfoAPI/" + id
});
return request;
};
});
The service has an $http dependency to perform HTTP GET, POST, PUT and DELETE operations.
Step 9: Now its time for us to add Angular controllers used for performing CRUD operations. We will add controllers to make call to the Angular service which we created in the Step 8.
ShowEmployeesController.js
//The controller has dependency upon the Service and ShareData
app.controller('ShowEmployeesController', function ($scope, $location, SinglePageCRUDService, ShareData) {
loadRecords();
//Function to Load all Employees Records.
function loadRecords()
{
var promiseGetEmployees = SinglePageCRUDService.getEmployees();
promiseGetEmployees.then(function (pl) { $scope.Employees = pl.data },
function (errorPl) {
$scope.error = 'failure loading Employee', errorPl;
});
}
//Method to route to the addemployee
$scope.addEmployee = function () {
$location.path("/addemployee");
}
//Method to route to the editEmployee
//The EmpNo passed to this method is further set to the ShareData.
//This value can then be used to communicate across the Controllers
$scope.editEmployee = function (EmpNo) {
ShareData.value = EmpNo;
$location.path("/editemployee");
}
//Method to route to the deleteEmployee
//The EmpNo passed to this method is further set to the ShareData.
//This value can then be used to communicate across the Controllers
$scope.deleteEmployee = function (EmpNo) {
ShareData.value = EmpNo;
$location.path("/deleteemployee");
}
});
The above controller is used to make call to SinglePageCRUDService using ‘loadRecords()’ function. This function make use of Promise object to manage asynchronous call to the service. The retrieved data is then saved in the $scope.Employees object, which is this then passed to the View. Functions addEmployee, editEmployee and deleteEmployee are used to Create, Edit and Delete employee respectively. $location is used to load the corresponding view based upon the route defined in the Module.
AddEmpController.js
app.controller('AddEmployeeController', function ($scope, SinglePageCRUDService) {
$scope.EmpNo = 0;
//The Save scope method used to define the Employee object and
//post the Employee information to the server by making call to the Service
$scope.save = function () {
var Employee = {
EmpNo: $scope.EmpNo,
EmpName: $scope.EmpName,
Salary: $scope.Salary,
DeptName: $scope.DeptName,
Designation: $scope.Designation
};
var promisePost = SinglePageCRUDService.post(Employee);
promisePost.then(function (pl) {
$scope.EmpNo = pl.data.EmpNo;
alert("EmpNo " + pl.data.EmpNo);
},
function (errorPl) {
$scope.error = 'failure loading Employee', errorPl;
});
};
});
The above ‘AddEmployeeController’, is dependent on the ‘SinglePageCRUDService’. The function ‘save’ makes call to the post() function service to post Employee object.
EditEmployeeController.js
app.controller("EditEmployeeController", function ($scope, $location,ShareData,SinglePageCRUDService) {
getEmployee();
function getEmployee() {
var promiseGetEmployee = SinglePageCRUDService.getEmployee(ShareData.value);
promiseGetEmployee.then(function (pl)
{
$scope.Employee = pl.data;
},
function (errorPl) {
$scope.error = 'failure loading Employee', errorPl;
});
}
//The Save method used to make HTTP PUT call to the WEB API to update the record
$scope.save = function () {
var Employee = {
EmpNo: $scope.Employee.EmpNo,
EmpName: $scope.Employee.EmpName,
Salary: $scope.Employee.Salary,
DeptName: $scope.Employee.DeptName,
Designation: $scope.Employee.Designation
};
var promisePutEmployee = SinglePageCRUDService.put($scope.Employee.EmpNo,Employee);
promisePutEmployee.then(function (pl)
{
$location.path("/showemployee");
},
function (errorPl) {
$scope.error = 'failure loading Employee', errorPl;
});
};
});
The above ‘EditEmployeeController’ is dependent on the ‘SinglePageCRUDService’ and ‘ShareData’ factory. The function getEmployee(), makes call to the getEmployee() function of the service and passes the ShareData.value to it. This is the EmpNo passed from the editEmployee() function from the ShowEmployeesController.
The function save() is used to make call to the put() function of the service by passing EmpNo and the Employee object to complete an edit operation. Both the save() and getEmployee() functions make use of promise object to manage asynchronous calls to angular service.
DeleteEmployeeController.js
app.controller("DeleteEmployeeController", function ($scope, $location, ShareData, SinglePageCRUDService) {
getEmployee();
function getEmployee() {
var promiseGetEmployee = SinglePageCRUDService.getEmployee(ShareData.value);
promiseGetEmployee.then(function (pl) {
$scope.Employee = pl.data;
},
function (errorPl) {
$scope.error = 'failure loading Employee', errorPl;
});
}
//The delete method used to make HTTP DELETE call to the WEB API to update the record
$scope.delete = function () {
var promiseDeleteEmployee = SinglePageCRUDService.delete(ShareData.value);
promiseDeleteEmployee.then(function (pl) {
$location.path("/showemployee");
},
function (errorPl) {
$scope.error = 'failure loading Employee', errorPl;
});
};
});
The above DeleteEmployeeController has a dependency on the SinglePageCRUDService and ShareData factory. The function getEmployee() makes a call to the getEmployee() function of the service and passes the ShareData.value to it. This is the EmpNo passed from the editEmployee() function from the ShowEmployeesController.
The function save() is used to make call to the delete() function of the service by passing EmpNo and Employee object to complete delete operation. Both the save() and getEmployee() functions make use of promise object to manage asynchronous calls to angular service.
Step 10: Now we need to manage our views for performing operations. We will design it as below:
Index.cshtml:
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />
<body ng-app="ApplicationModule">
<div>
<div>
<div>
<table>
<tr>
<td><a href="showemployees"> Show Employees </a></td>
<td><a href="addemployee"> Add Employee </a></td>
</tr>
</table>
</div>
<div>
<div ng-view></div>
</div>
</div>
</div>
</body>
@section scripts{
<script type="text/javascript" src="@Url.Content("~/Scripts/angular.js")"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/angular-route.min.js")"></script>
<script type="text/javascript" src="@Url.Content("~/MyScripts/Module.js")"></script>
<script src="~/MyScripts/Services.js"></script>
<script type="text/javascript" src="@Url.Content("~/MyScripts/ShowEmployeesController.js")"></script>
<script type="text/javascript" src="@Url.Content("~/MyScripts/AddEmpController.js")"></script>
<script type="text/javascript" src="@Url.Content("~/MyScripts/EditEmployeeController.js")"></script>
<script type="text/javascript" src="@Url.Content("~/MyScripts/DeleteEmployeeController.js")"></script>
}
In the above markup, <body> is bound with the ApplicationModule using ‘ng-app’ directive. The ‘ng-view’ will be used to show views which we will be loading dynamically. The Hyperlink elements define links for ‘showemployees’ and ‘addemployee’ route expressions to load corresponding views.
AddEmployee.cshtml
@{
ViewBag.Title = "AddEmployee";
}
<h2>Add New Employee</h2>
<table>
<tr>
<td>EmpNo</td>
<td><input type="text" ng-model="EmpNo" /> </td>
</tr>
<tr>
<td>EmpName</td>
<td><input type="text" ng-model="EmpName" /> </td>
</tr>
<tr>
<td>Salary</td>
<td><input type="text" ng-model="Salary" /> </td>
</tr>
<tr>
<td>DeptName</td>
<td><input type="text" ng-model="DeptName" /> </td>
</tr>
<tr>
<td>Designation</td>
<td><input type="text" ng-model="Designation" /> </td>
</tr>
</table>
<input type="button" value="Save" ng-click="save()" />
The above view uses ‘ng-model’ directive to bind Employee properties with the <input> elements. This view will be loaded with the /addemployee route expression. This is defined in the Module.js. This expression in the URL loads the AddEmployeeController. This controller contains method to post employee details to save in the database.
EditEmployee.cshtml
@{
ViewBag.Title = "EditEmployee";
}
<h2>EditEmployee</h2>
<table>
<tr>
<td>EmpNo</td>
<td><input type="text" ng-model="Employee.EmpNo" /> </td>
</tr>
<tr>
<td>EmpName</td>
<td><input type="text" ng-model="Employee.EmpName" /> </td>
</tr>
<tr>
<td>Salary</td>
<td><input type="text" ng-model="Employee.Salary" /> </td>
</tr>
<tr>
<td>DeptName</td>
<td><input type="text" ng-model="Employee.DeptName" /> </td>
</tr>
<tr>
<td>Designation</td>
<td><input type="text" ng-model="Employee.Designation" /> </td>
</tr>
</table>
<input type="button" value="Save" ng-click="save()" />
<div>{{error}}</div>
The above view is loaded for the /editemployee url. This is defined in the Module.js in the routing configuration and loads EditEmployeeController, which contains a method to fetch employees to be edited and update its values.
DeleteEmployee.cshtml
@{
ViewBag.Title = "DeleteEmployee";
}
<h2>DeleteEmployee</h2>
<table>
<tr>
<td>EmpNo</td>
<td>{{Employee.EmpNo}}</td>
</tr>
<tr>
<td>EmpName</td>
<td>{{Employee.EmpName}}</td>
</tr>
<tr>
<td>Salary</td>
<td>{{Employee.Salary}}</td>
</tr>
<tr>
<td>DeptName</td>
<td>{{Employee.DeptName}}</td>
</tr>
<tr>
<td>Designation</td>
<td>{{Employee.Designation}}</td>
</tr>
</table>
<input type="button" value="Delete" ng-click="delete()" />
The above view is loaded for the /deleteemployee url. This is defined in the Module.js in the routing configuration. This loads DeleteEmployeeController, which contains a method to fetch employee to be deleted and perform delete operations.
Running Application
Run the application the following View gets displayed
Click on ‘Show Employees’. The following result will be displayed with Employee details:
The URL used is http://Server/shoewmployees
Similarly click on the ‘Add Employee’ link and the following View will be displayed:
The URL is http://server/addemployee. Add the Employee details and click on the Save button, the EmpNo will be generated as follows:
Click on the ‘Show Employees’ link and the result will be as follows:
Click on the ‘Edit’ link of any row and the Edit View will be displayed:
The url will be http://Server/editemployee. Here we can update Employee details and click on save. After clicking on the Save we will be back to the first result (exactly the same when we ran the application) where the updated values can be verified. In a similar way, the delete functionality can also be tested.
Conclusion: SPA is the requirement of this new generation of web applications. It provides an easy interaction with WEB interfaces for users looking for a Desktop like experience on the Web. A mashup of client-server technologies like Angular.js and ASP.NET Web API helps you create enterprise ready SPA applications.
Download the entire source code of this article (Github)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Mahesh Sabnis is a DotNetCurry author and a Microsoft MVP having over two decades 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), and Front-end technologies like Angular and React. Follow him on twitter @
maheshdotnet or connect with him on
LinkedIn