Single Page CRUD Application (SPA) using ASP.NET Web API, MVC and Angular.js

Posted by: Mahesh Sabnis , on 11/15/2014, in Category ASP.NET
Views: 178588
Abstract: Using ASP.NET Web API, MVC and Angular.js to create a Single Page Application (SPA) that supports CRUD operations.

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:

ef-employee-mapping

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-appdirective. 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

webapi-angular-app

Click on ‘Show Employees’. The following result will be displayed with Employee details:

show-employees

The URL used is http://Server/shoewmployees

Similarly click on the ‘Add Employee’ link and the following View will be displayed:

add-employee

The URL is http://server/addemployee. Add the Employee details and click on the Save button, the EmpNo will be generated as follows:

emp-no-generation

Click on the ‘Show Employees’ link and the result will be as follows:

employess-angular-webapi-app

Click on the ‘Edit’ link of any row and the Edit View will be displayed:

edit-employee

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.

Absolutely Awesome Book on C# and .NET

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!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
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


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Avinash Patil on Monday, November 17, 2014 4:32 AM
Hi,

This article is not complete, I was trying it locally and seems to be some steps are missing. Please update the article with missing steps.
Comment posted by Avinash Patil on Monday, November 17, 2014 4:46 AM
Hi,

This article is not complete, I was trying it locally and seems to be some steps are missing. Please update the article with missing steps.
Comment posted by Mahesh Sabnis on Tuesday, November 18, 2014 1:11 AM
Hi Avinash,

The article only focuses on using Angular.js for MVC SPA. The code for WEB API is not given in the article. Also the steps for creating Company.mdf are not given in the article. So please download the code for article from the provided link.

Regards
Mahesh Sabnis  
Comment posted by Anand Rajput on Thursday, November 20, 2014 5:30 AM
It's not working, when i click Show Employees then it show {{Column Name}}
Comment posted by Anand Rajput on Thursday, November 20, 2014 5:32 AM
It's not working, when i click Show Employees then it show {{Column Name}}
Comment posted by jp hellemons on Wednesday, December 3, 2014 6:36 AM
step 2: I added core, so had to add routing too. Perhaps list the exact nuget package that you want to add?
step 10: what is in your Services.js file? You did not mention making this https://github.com/dotnetcurry/SPA-Angularjs-ASPNET-WebAPI/blob/master/MVC_Using_Angular/MyScripts/Services.js in your tutorial. I now see that step 8 should be put in services.js
how can you add a body tag in a view that has a shared view containing a body tag?
and why all those div tags?
Inline afbeelding 1
When I click on <td><a href="showemployees"> Show Employees </a></td> it gives me a blank page.
showemployees.cshtml is still an empty file. You did not mention the content. I took it from github https://github.com/dotnetcurry/SPA-Angularjs-ASPNET-WebAPI/blob/master/MVC_Using_Angular/Views/EmployeeInfo/ShowEmployees.cshtml
http://localhost:53353/showemployees gives me a 404, so there is something off at the routing part. Did I miss something?
Comment posted by christian on Monday, December 8, 2014 10:05 AM
Thanks for the nice article.
The sample from git-hub works.
Rewriting the sample with the current version of VS does not work https://docs.angularjs.org/error/$location/nobase
I am still trying to fix this.
Comment posted by Mahesh Sabnis on Monday, December 8, 2014 11:47 PM
Hi Anand Rajput,

Please check for the Angular.js Library whether it is getting loaded or not. Use Developer tools of the browsers  for it.

Regards
Mahesh Sabnis
Comment posted by Mahesh Sabnis on Tuesday, December 9, 2014 12:09 AM
Hi jp hellemons,

Please download the code from the link. I thing your routing is not getting executed.

Regards
Mahesh
Comment posted by Siddharth Kumar Tathe on Wednesday, December 17, 2014 1:59 AM
This Project is not working but its good for how to use javascript controller
Comment posted by Sidharth on Friday, January 9, 2015 4:45 AM
give me a app with out sap
Comment posted by Jirarat Wongthirawat on Tuesday, January 13, 2015 12:13 AM
Place "<base href="/">" in "<head>" tag in _Layout.cshtml file.

It work well for me. ^^
Comment posted by Jirarat Wongthirawat on Tuesday, January 13, 2015 12:20 AM
Add "AngularJS Core" and "AngularJS Route" from NuGet too.
Comment posted by riapp.net on Saturday, February 14, 2015 1:23 AM
Also consider to add <base href="/"> to the '_Layout.cshtml' if you are using latest angular js otherwise you get this error https://docs.angularjs.org/error/$location/nobase
Comment posted by Filip on Saturday, February 14, 2015 10:38 PM
Nice tip riapp
Comment posted by l;l on Thursday, March 19, 2015 6:53 AM
lj
Comment posted by Joe on Tuesday, April 7, 2015 1:26 PM
Well Done Mahesh.  This is an excellent example of how to integrate Angular with .Net.  You cleared up a lot of confusion.  

Thanks

Joe
Comment posted by subhash on Sunday, April 12, 2015 4:42 AM
not working in my case
Comment posted by Leanord on Wednesday, April 15, 2015 9:45 AM
When i refresh that same page it is showing 404 error.

if you know give some idea
Comment posted by Leanord on Wednesday, April 15, 2015 9:50 AM
Hi Mahesh

CRUD apps is working perfectly for me but the problem is when i reload that same page it goes to 404 error.
Comment posted by Leanord on Wednesday, April 15, 2015 9:50 AM
Hi Mahesh

CRUD apps is working perfectly for me but the problem is when i reload that same page it goes to 404 error.
Comment posted by gopi on Wednesday, April 22, 2015 7:43 AM
It's not working, when i click Show Employees then it shows table content as {{Column Name}}
please say what should i include...