Introduction
In this article, we are going to implement CRUD operations using one of the most popular client side libraries "AngularJs" and apply business logic using ASP.NET MVC with Entity Framework & DB first approach.
Progress
We will start by creating a brand new ASP.NET MVC project and progress as follows:
- Add AngularJs library to our project, bundle it and enable optimization
- Add Bundle reference to the layout page and check to see if Angular is working or not
- Add Contact List client side functionality
- Adding EF (edmx) using Database first approach and generate POCO classes
- Create Repository class with Linq statements for all CRUD operations
- Create a custom Angular Service "Repo" to access server side controller and
- Finally, create our custom Angular Directive with a template
Creating a Brand New ASP.NET MVC Project
Start by creating a new ASP.NET MVC project. For this demo, I used VS 2013 with including MVC core and NO Web API and Web Forms.
Since our primary focus for this article is on AngularJs and to eliminate unnecessary code, click on “Change Authentication” button to set authentication to “No Authentication”. This will remove all unnecessary code related to authentication either using Windows/Forms or OWIN.
Our project initially looks like this with “Home” Controller and its related views in the “Views” folder.
Now, we will create a new MVC 5 empty controller “Ng
” to the Controllers folder.
Note: Creating a controller will automatically add new folder within "Views" folder to hold all its corresponding views. If not, create a folder with the same name as controller name in the "Views" folder.
Now, we will add our first action method "NgFirst
" to our new controller.
[HttpGet]
public ActionResult NgFirst()
{
return View();
}
Go and create our “NgFirst
” View by right clicking on the View()
; and select “Add View…”. Use our existing layout page as its master layout.
This is how our solution looks after adding the controller "Ng
" and a view "NgFirst
".
Add Angularjs Library to Our Project, Bundle It and Enable Optimization
Now, it’s time to add AngularJS library to our project using “NuGet Package Manager”. Right click on project references and click on “Manage NuGet Packages.."
Search for “AngularJs” and NuGet will list out all AngularJs related items. For this demo, we only need “Core” features available with the name AngularJS Core.
Installing the AngularJs Core will add the following JavaScript files to the Scripts folder.
Open “BundleConfig.cs” file located in “App_Start” folder to create virtual path “ng
” to point to all Angular js file.
bundles.Add(new ScriptBundle("~/bundles/ng")
.Include(
"~/Scripts/angular.js",
"~/Scripts/angular-mocks.js"));
BundleTable.EnableOptimizations = false;
Add Bundle Reference to the Layout Page and Check to See if AngularJs is Working or Not
Open “_Layout.cshtml” file to add the newly created bundle virtual path name “ng
”.
Note: Before @RenderSection
tag and after bootstrap.
@Scripts.Render("~/bundles/ng")
Also, add new Action link “{{ng-First}}
” to the top navigation menu bar on layout page.
<li>@Html.ActionLink("{{NgFirst}}", "NgFirst", "Ng")</li>
Check to see if everything related to AngularJs and bundle is set properly, add the following markup to our “NgFirst
” view. Build and run the project.
<div ng-app>
<p>{{ "Hello" + " World" }}</p>
<p>{{ 2 + 4 }}</p>
</div>
Build and run the application to view our “NgFirst
” view. Upon inspecting with Firebug, it should appear as follows:
It means that our AngularJs library has been loaded perfectly and successfully evaluated the statements.
Note: If AngularJs doesn’t load properly, our view will look like this:
Add "Contact List" Client Side Functionality
In our first application, we are going to create a Contact List form where we get three fields (First name, last name and email) from the user and save it to the list using Angular controller.
Note: We are NOT using any server side MVC or API controllers for now. All we are doing is persist data in array object on client side.
Add the following HTML markup to the page:
<div ng-app="ContactList" ng-controller="ContactCtrl">
<h2>Contact Details</h2>
<form>
<div class="col-sm-12">
<div><input type="hidden" ng-model="c.Id" /></div>
<div class="col-sm-3"><input type="text"
placeholder="First Name" class="form-control"
ng-model="c.FirstName" id="firstName" /></div>
<div class="col-sm-3"><input type="text"
placeholder="Last Name" class="form-control"
ng-model="c.LastName" /></div>
<div class="col-sm-3"><input type="email"
placeholder="<a href="mailto:email@somedomain.com">email@somedomain.com</a>"
class="form-control" ng-model="c.Email" /></div>
<div class="col-sm-3">
<input type="submit" value="Save"
class="btn btn-primary" ng-click="addContact(c);"
ng-hide="IsInEditMode" />
<input type="submit" value="Edit"
class="btn btn-primary" ng-click="editContact(c);"
ng-show="IsInEditMode" /><input type="button" value="Cancel"
class="btn btn-default" ng-click="clearFields();" />
</div>
</div>
</form><br />
<div ng-show="haveContacts();">
<h2>Contact List</h2>
<hr />
<table class="table table-striped">
<thead>
<tr>
<td>Id</td>
<td>Name</td>
<td>Email</td>
<td></td>
</tr>
</thead>
<tbody>
<tr ng-repeat="contact in contacts">
<td>{{contact.Id}}</td>
<td>{{contact.FirstName + " " + contact.LastName}}</td>
<td><span ng-bind="contact.Email"></span></td>
<td>
<button type="button" class="btn btn-primary btn-xs"
ng-click="doEdit(contact);">
<span class="glyphicon glyphicon-edit"
aria-hidden="true"></span> Edit
</button>
<button type="button" class="btn btn-danger btn-xs"
ng-click="deleteContact(contact.Id);">
<span class="glyphicon glyphicon-remove"
aria-hidden="true">
</span> Delete
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
Also add the following script to the bottom of the View:
Before adding JavaScript to the view, let us first add a scripts section to our layout page right after the bundle script statements. This allows us to render all JavaScript that is placed within the script section in child/container view to appear right after layout scripts.
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/bundles/ng")
@RenderSection("scripts", required: false)
The "required: false
" indicates that JavaScript section in view is not mandatory. If not, it will throw an error.
Warning: All the script that is used within the views must be placed inside the script section. Otherwise, your script may not work as intended.
@section scripts{
<script>
var myContact = angular.module("ContactList", []);
myContact.controller("ContactCtrl",
["$scope", "$window", function ($scope, $window) {
$scope.contacts = [{ Id: 1, FirstName: "Sree",
LastName: "M", Email: "abc@bbc.com" }];
$scope.c = { Id: 1, FirstName: "Sree",
LastName: "M", Email: "abc@bbc.com" };
$scope.IsInEditMode = false;
clearFields = function () {
$scope.c.Id = "";
$scope.c.FirstName = "";
$scope.c.LastName = "";
$scope.c.Email = "";
$scope.IsInEditMode = false;
};
$scope.clearFields = clearFields;
addContact = function (contact) {
if ((contact.FirstName === "") ||
(contact.LastName === "") || (contact.Email === "")) {
$window.alert("Input fields cannot be empty!");
$("#firstName").focus();
}
else {
var localContact = angular.copy(contact);
if ($scope.contacts.length === 0)
localContact.Id = 1;
else
localContact.Id = $scope.contacts[$scope.contacts.length - 1].Id + 1;
$scope.contacts.push(localContact);
clearFields();
}
};
$scope.addContact = addContact;
doEdit = function (contact) {
var localCopy = angular.copy(contact);
$scope.c.Id = localCopy.Id;
$scope.c.FirstName = localCopy.FirstName;
$scope.c.LastName = localCopy.LastName;
$scope.c.Email = localCopy.Email;
$scope.IsInEditMode = true;
}
$scope.doEdit = doEdit;
editContact = function (xContact) {
var localCopy = angular.copy(xContact);
for (var i = 0; i < $scope.contacts.length; i++) {
if (xContact.Id === $scope.contacts[i].Id) {
$scope.contacts[i].Id = localCopy.Id;
$scope.contacts[i].FirstName = localCopy.FirstName;
$scope.contacts[i].LastName = localCopy.LastName;
$scope.contacts[i].Email = localCopy.Email;
clearFields();
return;
}
}
$scope.contacts.push(localCopy);
clearFields();
}
$scope.editContact = editContact;
deleteContact = function (Id) {
if ($window.confirm("Are you sure you want to delete the contact?")) {
for (var i = 0; i < $scope.contacts.length; i++) {
if (Id === $scope.contacts[i].Id) {
$scope.contacts.splice(i, 1);
break;
}
}
}
}
$scope.deleteContact = deleteContact;
haveContacts = function () {
return $scope.contacts.length > 0 ? true : false;
}
$scope.haveContacts = haveContacts;
}]);
</script>
}
Following are the Ng Directives we used in our view:
ng-app
: This signifies that we have an Angular container declared on this page and everything within the element decorated with ng-app
should be ready to parse Angular directives and evaluate {{ }}
syntax at runtime by the browser. ng-controller
: The only controller which is responsible to control the UI. It should contain only the business logic needed for a single view. ng-model
: The ng-model
directive binds an input, select, textarea (or custom form control) to a property on the scope. It acts as a two way binding directive. ng-model
will try to bind to the property given by evaluating the expression on the current scope. If the property doesn't already exist on this scope, it will be created implicitly and added to the scope. ng-click
: This directive allows us to assign scope level object/function to the client side click event of an element. ng-hide/ng-show
: The ng-hide
/ng-show
directive shows or hides the given HTML element based on the expression provided to the ng-hide
/ng-show
attribute. ng-repeat
: Allows us to loop through the item collection and apply template to each item. ng-bind
: The attribute tells Angular to replace the content of the element with the value of a given expression or scope level variable, and to update the content whenever there is a change in expression.
Following are the Ng Services we used in our view:
$scope
: This service provides separation between the model and the view, via a mechanism for watching the model for changes. They also provide an event emission/broadcast and subscription facility. $window
: Refers to the browser window. This service is similar to window object in JavaScript.
Following are the Ng Methods we used in our view:
Angular.copy
: Creates a deep copy of source, which should be an object or an array. This allows us not to copy the object along with its angular two way bindings. - Code Explanation: In this sample, we have created a
$scope
level object “c
” which will holds current values of Id, First name, Last name and email. An array object “contacts
” to hold list of contacts added and a flag variable “IsInEditMode
” to hold the form editable mode. We also create container methods to perform add, edit and delete operations all at client side.
The JavaScript code itself explains a lot. It does basic CRUD operations client side.
Adding EF (edmx) using Database First Approach and Generate POCO Classes
Now, we will add server side behavior (CRUD) using ASP.NET MVC. We use our existing “NgController
” for all CRUD operations while creating a new entity model (.edmx) which will point to SQL Server database.
Let us first create a new database "FirstAngularJs
". Use the script below to create table "[User]
":
USE [FirstAngularJs]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[User](
[Id] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [nvarchar](50) NOT NULL,
[LastName] [nvarchar](50) NOT NULL,
[Email] [nvarchar](100) NULL,
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Now, we will add “ADO.NET Entity Data Model” to the Model folder with the name “Model.edmx”. Select “EF Designer from Database” from the Wizard.
Click “Next” and select Entity Framework 6.x version for this demo.
Note: In this application, I made use of EF version 6. However, we can choose other versions too.
Connect to your database to select "User
" table. Also specify the Connection string name that appears in web.config file.
Note: The connection string name given here will be the same for our entity model context.
Click “Next” again and select the table which we created earlier in SQL Server Management Studio. Uncheck “Pluralize/…” and set the namespace to our model. Click “Finish” to generate POCO (Plain Old CLR Objects) class.
Note: Code generation tool T4 (Text Template Transformation Toolkit) will produce some warning messages before actually creating our POCO classes. Click “OK” to continue.
Once everything is done correctly, our Entity Model will look like this:
Create Repository Class with Linq Statements for all CRUD Operations
Next, we will add “Repository” folder to the project root which usually contains classes to interact with our database . We will add a class “UserRepository.cs” with CRUD operations using Linq.
public class UserRepository
{
private ngFirstEntitiesConStr _context;
public UserRepository()
{
_context = new ngFirstEntitiesConStr();
}
public bool CreateUser(User model)
{
_context.User.Add(model);
return (_context.SaveChanges() > 0);
}
public List<User> GetAllUsers()
{
return _context.User.ToList();
}
public User GetUserById(int Id)
{
return _context.User.Where(u => u.Id == Id).SingleOrDefault();
}
public bool UpdateUser(User usr)
{
User userToUpdate = _context.User.Where(u => u.Id == usr.Id).SingleOrDefault();
userToUpdate.FirstName = usr.FirstName;
userToUpdate.LastName = usr.LastName;
userToUpdate.Email = usr.Email;
_context.Entry(userToUpdate).State = System.Data.Entity.EntityState.Modified;
return (_context.SaveChanges() > 0);
}
public bool DeleteUser(int Id)
{
try
{
User ItemToDelete = _context.User.Where(u => u.Id == Id).SingleOrDefault();
_context.Entry(ItemToDelete).State = System.Data.Entity.EntityState.Deleted;
return (_context.SaveChanges() > 0);
}
catch (Exception)
{
return false;
}
}
}
Now, update our “NgController
” with new action methods to create, update, delete and get user contacts from the repository as follows:
UserRepository repo = new UserRepository();
[HttpPost]
public JsonResult CreateUser(User user)
{
return Json(repo.CreateUser(user));
}
[HttpGet]
public JsonResult GetUsers()
{
return Json(repo.GetAllUsers(), JsonRequestBehavior.AllowGet);
}
[HttpGet]
public JsonResult GetUser(int id)
{
return Json(repo.GetUserById(id), JsonRequestBehavior.AllowGet);
}
[HttpPost]
public JsonResult UpdateUser(User user)
{
return Json(repo.UpdateUser(user));
}
[HttpPost]
public JsonResult DeleteUser(int Id)
{
return Json(repo.DeleteUser(Id), JsonRequestBehavior.AllowGet);
}
Unlike traditionally access our controller action methods using jQuery Ajax calls, we now rely on Angular services ($http). This is crucial as we are going to create custom Angular service with $http
dependency.
Note: We need to make Angular controller independent of the logic related to accessing remote server methods, we have to use custom Angular service to do the heavy lifting.
$http: This service is a core Angular service that facilitates communication with the remote HTTP servers via the browser's XMLHttpRequest
object or via JSONP.
Add Custom Angular Service to Our Script Just After Creating the Angular Module
myApp.service("Repo", ["$http", function ($http) {
this.addUser = function (usr) {
var userObj = { "Id": usr.Id, "FirstName":
usr.FirstName, "LastName": usr.LastName, "Email": usr.Email }
var config = { method: "POST",
url: "@Url.Action("CreateUser", "Ng")", data: userObj };
return $http(config);
};
this.GetUsers = function () {
return $http.get("@Url.Action("GetUsers", "Ng")");
};
this.GetUser = function (id) {
var config = { method: "GET",
url: "@Url.Action("GetUser", "Ng")", data: Id };
$http(config)
.success(function (data, status, header, config) {
alert(data);
}).error(function (data, status, header, config) {
alert("Error: Something went wrong");
});
};
this.editUser = function (usr) {
var userObj = { "Id": usr.Id, "FirstName":
usr.FirstName, "LastName": usr.LastName, "Email": usr.Email };
var config = { method: "POST",
url: "@Url.Action("UpdateUser", "Ng")", data: userObj };
return $http(config);
};
this.deleteUser = function (id) {
var userObj = { "Id": id };
var config = { method: "POST",
url: "@Url.Action("DeleteUser", "Ng")", data: userObj };
return $http(config);
};
}]);
If we look at our “Repo
“ service, we are injecting $http
service into our handler. Also, we have several methods to interact with the server side action methods. All we are doing is configuring the request method and to call the $http
service.
Basically to configure $http
service, we need to set the action URL, Method type (GET/POST) and with optional data to post to the server. Here is an example to call "CreateUser
" action method in "Ng
" controller with contact details as "userObj
" with a POST
method type.
var userObj = { "Id": usr.Id, "FirstName":
usr.FirstName, "LastName": usr.LastName, "Email": usr.Email }
var config = { method: "POST",
url: "@Url.Action("CreateUser", "Ng")", data: userObj };
return $http(config);
Note: $http(config)
will return a promise. Using promise, we can access success and/or error callback methods.
Now, we need to update our "ContactCtrl
" controller to make use of our custom Angular service "Repo
". We do this by injecting our custom service "Repo
" into our controller object.
myContact.controller("ContactCtrl",
["$scope", "$window", function ($scope, $window) {
$scope.contacts = [];
$scope.c = { Id:"" ,
FirstName: "", LastName: "", Email: "" };
$scope.IsInEditMode = false;
clearFields = function () {
$scope.c.Id = "";
$scope.c.FirstName = "";
$scope.c.LastName = "";
$scope.c.Email = "";
$scope.IsInEditMode = false;
};
$scope.clearFields = clearFields;
addContact = function (contact) {
Repo.addUser(usr)
.success(function (data, status, header, config) {
getUsers();
clearFields();
$scope.isEditMode = false;
$("#firstName").focus();
})
.error(function (data, status, header, config) {
alert("Error: Something went wrong");
});
(contact.LastName === "") || (contact.Email === "")) {
};
$scope.addContact = addContact;
doEdit = function (contact) {
var localCopy = angular.copy(contact);
$scope.c.Id = localCopy.Id;
$scope.c.FirstName = localCopy.FirstName;
$scope.c.LastName = localCopy.LastName;
$scope.c.Email = localCopy.Email;
$scope.IsInEditMode = true;
}
$scope.doEdit = doEdit;
getUsers = function () {
Repo.GetUsers()
.success(function (data, status, header, config) {
$scope.users = data;
})
.error(function (data, status, header, config) {
alert("Error: Something went wrong");
})
};
editContact = function (xContact) {
Repo.editUser(usr).success(function (data, status, header, config) {
getUsers();
clearFields();
$scope.isEditMode = false;
})
.error(function (data, status, header, config) {
alert("Error: Something went wrong");
});
}
$scope.editContact = editContact;
deleteContact = function (Id) {
if ($window.confirm("Are you sure you want to delete the user
'" + usr.FirstName + " " + usr.LastName + "'?")) {
Repo.deleteUser(usr.Id).success(function (data, status, header, config) {
getUsers();
clearFields();
$scope.isEditMode = false;
}).error(function (data, status, header, config) {
alert("Error: Something went wrong");
});
}
}
$scope.deleteContact = deleteContact;
haveContacts = function () {
return $scope.contacts.length > 0 ? true : false;
}
$scope.haveContacts = haveContacts;
clearFields();
getUsers();
}]);
Build and run the application to see the changes. Perform some Create, Read, Update and Delete operations and check your back end database.
Create Our Custom Angular Directive with a Template
This first step is to create our custom Angular Directive "contactdir" at the bottom of the script section; right exactly where our controller ends.
myContact.directive("contactdir", function () {
return {
restrict: 'EA',
replace: true,
templateUrl: "@Url.Content("/templates/contactrow.html")"
};
});
The directive constructor function will return an object with the following properties set:
restrict
: This allows us to restrict custom directive to be used as an element or attribute. Although, we can use directive as a CSS class name or comment; In our case, we restricted it to an element and attribute. replace
: Setting this value to 'true
' will replace the inline element or attribute; 'false
' value will append template content to the underlying attribute or element. template
: Inline string
with scope variables used as a template for the directive. templateUrl
: Path to an external file which holds the template content. Our "templateUrl
" is pointing to an external HTML file located within "Templates" folder.
Go and create a new folder “Templates” to our project root. Extract our tables template row within tbody
element to a new HTML file (ContactRow.html).
Note: Make sure you remove ng-Repeat
from the tr
tag. Let everything remain as it is.
<tr>
<td>{{contact.Id}}</td>
<td>{{contact.FirstName + " " + contact.LastName}}</td>
<td><span ng-bind="contact.Email"></span></td>
<td>
<button type="button" class="btn btn-primary btn-xs" ng-click="doEdit(contact);">
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span> Edit
</button>
<button type="button" class="btn btn-danger btn-xs" ng-click="deleteContact (contact);">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Delete
</button>
</td>
</tr>
Update tbody
element in the "NgFirst.cshtml" to make use of our directive as follows:
<tr ng-repeat="contact in contacts" contactdir></tr>
Note: We are passing Angular scope variable "contact
" with the help of ng-repeat
directive to our custom "contactdir
" directive.
Go and run the application to see no changes in the UI but instead changes in the underlying contact listing using directive.
Records stored in our MS SQL Server database.
Finally, our NgFirst
project with Controller (NgController
), Model (Model.edmx), Repository (UserReposiroty.cs) and a View (NgFirst.cshtml) looks like this:
Points of Interest
As part of our first Angular js application with ASP.NET MVC and Entity Framework, we made use of the following Angular specific directives, services or methods:
- ng-app
- ng-controller
- ng-directive
- ng-model
- ng-bind or {{ expression }}
- ng-click
- ng-hide
- ng-show
- ng-repeat
- $scope
- $http
- $window
- angular.module
- angular.copy
I hope with this article you have learnt something new and could perform some basic operations using AngularJs. Feel free to rate, comment and give suggestions.