A Simple SPA with AngularJs, ASP.NET MVC, Web API And EF

6 minute read

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!

Source code