A Simple SPA with AngularJs, ASP.NET MVC, Web API And EF
SPA stands for Single Page Application. Here I will demonstrate a simple SPA with ASP.NET MVC, Web API and Entity Framework. I will show a trainer profile and its CRUD operation using AngularJS, ASP.NET MVC, Web api and Entity Framework.
Step 1: Create a ASP.NET MVC application with empty template
- Open visual studio, Got to File->New->Project
- Select Template -> Visual C# -> Web -> ASP.NET MVC 4 Web application and click OK
- Select Empty Template and Razor as view engine
Step 2: Install required nuget packages
- Run the following command in Package Manager Console (Tools->Library Package Manager->Package Manager Console) to install required package.
- Make sure your internet connection is enabled.
PM> Install-Package jQuery
PM> Install-Package angularjs -Version 1.2.26
PM> Install-Package Newtonsoft.Json
PM> Install-Package MvcScaffolding
Step 3: Create Connection String
Create connection string and name DB name as SPADB
<connectionStrings>
<add name="SPAContext" connectionString="Data Source=.\sqlexpress;Initial Catalog=SPADB;Integrated Security=SSPI;" providerName="System.Data.SqlClient" />
</connectionStrings>
Step 4: Create model class
- Create Trainer model
public class Trainer
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Venue { get; set; }
}
Step 5: Create Repository
- Create repository for Trainer model.
- Run the following command in Package Manager Console (Tools->Library Package Manager->Package Manager Console) to create repository.
- I used repository pattern for well-structured and more manageable.
PM> Scaffold Repository Trainer
After running the above command you will see SPAContext.cs and TrainerRepository.cs created in Model folder. For well manageability, I create a directory name Repository and put these two files in the Repository folder. So change the namespace as like SPA.Repository instead of SPA.Model. I also create a UnitOfWork for implement unit of work pattern.
The overall folder structure looks like below.
SPAContext.cs
public class SPAContext : DbContext
{
public SPAContext()
: base("SPAContext")
{
}
// You can add custom code to this file. Changes will not be overwritten.
//
// If you want Entity Framework to drop and regenerate your database
// automatically whenever you change your model schema, add the following
// code to the Application_Start method in your Global.asax file.
// Note: this will destroy and re-create your database with every model change.
//
// System.Data.Entity.Database.SetInitializer(new System.Data.Entity.DropCreateDatabaseIfModelChanges<SPA.Models.SPAContext>());
public DbSet<SPA.Models.Trainer> Trainers { get; set; }
public DbSet<SPA.Models.Training> Trainings { get; set; }
}
TrainerRepository.cs
public class TrainerRepository : ITrainerRepository
{
SPAContext context = new SPAContext();
public TrainerRepository()
: this(new SPAContext())
{
}
public TrainerRepository(SPAContext context)
{
this.context = context;
}
public IQueryable<Trainer> All
{
get { return context.Trainers; }
}
public IQueryable<Trainer> AllIncluding(params Expression<Func<Trainer, object>>[] includeProperties)
{
IQueryable<Trainer> query = context.Trainers;
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query;
}
public Trainer Find(long id)
{
return context.Trainers.Find(id);
}
public void InsertOrUpdate(Trainer trainer)
{
if (trainer.Id == default(long))
{
// New entity
context.Trainers.Add(trainer);
}
else
{
// Existing entity
context.Entry(trainer).State = System.Data.Entity.EntityState.Modified;
}
}
public void Delete(long id)
{
var trainer = context.Trainers.Find(id);
context.Trainers.Remove(trainer);
}
public void Save()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
public interface ITrainerRepository : IDisposable
{
IQueryable<Trainer> All { get; }
IQueryable<Trainer> AllIncluding(params Expression<Func<Trainer, object>>[] includeProperties);
Trainer Find(long id);
void InsertOrUpdate(Trainer trainer);
void Delete(long id);
void Save();
}
UnitOfWork.cs
public class UnitOfWork : IDisposable
{
private SPAContext context;
public UnitOfWork()
{
context = new SPAContext();
}
public UnitOfWork(SPAContext _context)
{
this.context = _context;
}
private TrainingRepository _trainingRepository;
public TrainingRepository TrainingRepository
{
get
{
if (this._trainingRepository == null)
{
this._trainingRepository = new TrainingRepository(context);
}
return _trainingRepository;
}
}
private TrainerRepository _trainerRepository;
public TrainerRepository TrainerRepository
{
get
{
if (this._trainerRepository == null)
{
this._trainerRepository = new TrainerRepository(context);
}
return _trainerRepository;
}
}
public void Dispose()
{
context.Dispose();
GC.SuppressFinalize(this);
}
}
Step 6: Add migration
- Run the following command to add migration
PM> Enable-Migrations
PM> Add-Migration initialmigration
PM> Update-Database –Verbose
Step 7: Create API Controllers
- Create Trainers api Controllers by clicking right button on Controller folder
- Choose API controller with empty read/write actions as scaffolding template
- Click Add button.
Step 8: Modify Controllers
- Now modify the controllers as follows. Here I used unit of work pattern.
public class TrainersController : ApiController
{
private UnitOfWork unitOfWork = new UnitOfWork();
public IEnumerable<Trainer> Get()
{
List<Trainer> lstTrainer = new List<Trainer>();
lstTrainer = unitOfWork.TrainerRepository.All.ToList();
return lstTrainer;
}
//// GET api/trainers/5
public Trainer Get(int id)
{
Trainer objTrainer = unitOfWork.TrainerRepository.Find(id);
return objTrainer;
}
public HttpResponseMessage Post(Trainer trainer)
{
if (ModelState.IsValid)
{
unitOfWork.TrainerRepository.InsertOrUpdate(trainer);
unitOfWork.TrainerRepository.Save();
return new HttpResponseMessage(HttpStatusCode.OK);
}
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
private IEnumerable<string> GetErrorMessages()
{
return ModelState.Values.SelectMany(x => x.Errors.Select(e => e.ErrorMessage));
}
// PUT api/trainers/5
public HttpResponseMessage Put(int Id, Trainer trainer)
{
if (ModelState.IsValid)
{
unitOfWork.TrainerRepository.InsertOrUpdate(trainer);
unitOfWork.TrainerRepository.Save();
return new HttpResponseMessage(HttpStatusCode.OK);
}
else
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
// DELETE api/trainers/5
public HttpResponseMessage Delete(int id)
{
Trainer objTrainer = unitOfWork.TrainerRepository.Find(id);
if (objTrainer == null)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
unitOfWork.TrainerRepository.Delete(id);
unitOfWork.TrainerRepository.Save();
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
Step 9: Create Layout and Home Controller
- Create _Layout.cshtml in Views->Shared folder
- Create HomeController and create inext view of Home controller by right click on index action and add view. You will see index.cshtml is created in Views->Home
HomeController.cs
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
_Layout.cshtml
<html ng-app="registrationModule">
<head>
<title>Training Registration</title>
</head>
<body>
@RenderBody()
</body>
</html>
Index.cshtml
@{
ViewBag.Title = "Home";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Home</h2>
<div>
<ul>
<li><a href="/Registration/Trainers">Trainer Details</a></li>
<li><a href="/Registration/Trainers/add">Add New Trainer</a></li>
</ul>
</div>
<div ng-view></div>
Step 10: Create registrationModule
- Create registrationModule.js in Scripts->Application. This is for angularjs routing.
var registrationModule = angular.module("registrationModule", ['ngRoute', 'ngResource'])
.config(function ($routeProvider, $locationProvider) {
$routeProvider.when('/Registration/Trainers', {
templateUrl: '/templates/trainers/all.html',
controller: 'listTrainersController'
});
$routeProvider.when('/Registration/Trainers/:id', {
templateUrl: '/templates/trainers/edit.html',
controller: 'editTrainersController'
});
$routeProvider.when('/Registration/Trainers/add', {
templateUrl: '/templates/trainers/add.html',
controller: 'addTrainersController'
});
$routeProvider.when("/Registration/Trainers/delete/:id", {
controller: "deleteTrainersController",
templateUrl: "/templates/trainers/delete.html"
});
$locationProvider.html5Mode(true);
});
Step 11: Create trainerRepository
- Create trainerRepository.js in Scripts->Application->Repository. This increase manageability for large application.
'use strict';
//Repository for trainer information
registrationModule.factory('trainerRepository', function ($resource) {
return {
get: function () {
return $resource('/api/Trainers').query();
},
getById: function (id) {
return $resource('/api/Trainers/:Id', { Id: id }).get();
},
save: function (trainer) {
return $resource('/api/Trainers').save(trainer);
},
put: function (trainer) {
return $resource('/api/Trainers', { Id: trainer.id },
{
update: { method: 'PUT' }
}).update(trainer);
},
remove: function (id) {
return $resource('/api/Trainers').remove({ Id: id });
}
};
});
Step 12: Create trainerController
- Create trainerController.js in Scripts->Application->Controllers
'use strict';
//Controller to get list of trainers informaion
registrationModule.controller("listTrainersController", function ($scope, trainerRepository, $location) {
$scope.trainers = trainerRepository.get();
});
//Controller to save trainer information
registrationModule.controller("addTrainersController", function ($scope, trainerRepository, $location) {
$scope.save = function (trainer) {
trainer.Id = 0;
$scope.errors = [];
trainerRepository.save(trainer).$promise.then(
function () { $location.url('Registration/Trainers'); },
function (response) { $scope.errors = response.data; });
};
});
//Controller to modify trainer information
registrationModule.controller("editTrainersController", function ($scope,$routeParams, trainerRepository, $location) {
$scope.trainer = trainerRepository.getById($routeParams.id);
$scope.update = function (trainer) {
$scope.errors = [];
trainerRepository.put(trainer).$promise.then(
function () { $location.url('Registration/Trainers'); },
function (response) { $scope.errors = response.data; });
};
});
//Controller to delete trainer information
registrationModule.controller("deleteTrainersController", function ($scope, $routeParams, trainerRepository, $location) {
trainerRepository.remove($routeParams.id).$promise.then(
function () { $location.url('Registration/Trainers'); },
function (response) { $scope.errors = response.data; });
});
Step 13: Create templates
- Create all.html, add.html, edit.html, delete.html in templateds->trainers folder.
All.html
<div>
<div>
<h2>Trainers Details</h2>
</div>
</div>
<div>
<div>
<table>
<tr>
<th>Name </th>
<th>Email </th>
<th>Venue </th>
<th>Action</th>
</tr>
<tr ng-repeat="trainer in trainers">
<td></td>
<td></td>
<td></td>
<td>
<a title="Delete" ng-href="/Registration/Trainers/delete/">Delete</a>
|<a title="Edit" ng-href="/Registration/Trainers/">Edit</a>
</td>
</tr>
</table>
</div>
</div>
Add.html
<form name="trainerForm" novalidate ng-submit="save(trainer)">
<table>
<tbody>
<tr>
<td>Trainer Name
</td>
<td>
<input name="name" type="text" ng-model="trainer.name" required ng-minlength="5" />
</td>
</tr>
<tr>
<td>Email
</td>
<td>
<input name="email" type="text" ng-model="trainer.email" />
</td>
</tr>
<tr>
<td>Venue
</td>
<td>
<input name="venue" type="text" ng-model="trainer.venue" />
</td>
</tr>
<tr>
<td class="width30"></td>
<td>
<input type="submit" value="Save" ng-disabled="trainerForm.$invalid" />
<a href="/Registration/Trainers" class="btn btn-inverse">Cancel</a>
</td>
</tr>
</tbody>
</table>
</form>
Edit.html
<form name="trainerFrom" novalidate abide>
<table>
<tbody>
<tr>
<td>
<input name="id" type="hidden" ng-model="trainer.id"/>
</td>
</tr>
<tr>
<td>Name
</td>
<td>
<input name="name" type="text" ng-model="trainer.name" />
</td>
</tr>
<tr>
<td>Email
</td>
<td>
<input name="email" type="text" ng-model="trainer.email" />
</td>
</tr>
<tr>
<td>Venue
</td>
<td>
<textarea name="venue" ng-model="trainer.venue"></textarea>
</td>
</tr>
<tr>
<td class="width30"></td>
<td>
<button type="submit" class="btn btn-primary" ng-click="update(trainer)" ng-disabled="trainerFrom.$invalid">Update</button>
</td>
</tr>
</tbody>
</table>
</form>
Step 14: Add references to the Layout
- Modify the _Layout.cshtml to add references
_Layout.cshtml
<html ng-app="registrationModule">
<head>
<title>Training Registration</title>
<script src="~/Scripts/angular.min.js"></script>
<script src="~/Scripts/angular-resource.min.js"></script>
<script src="~/Scripts/angular-route.min.js"></script>
<script src="~/Scripts/jquery-2.1.1.min.js"></script>
<script src="~/Scripts/Application/registrationModule.js"></script>
<script src="~/Scripts/Application/Repository/trainerRepository.js"></script>
<script src="~/Scripts/Application/Controllers/trainersController.js"></script>
</head>
<body>
@RenderBody()
</body>
</html>
Now run you application and add, delete, modify and get all trainer information. Thanks for your patient!