In the first part of the series, I described how to configure the application to include the requisite JavaScript files, laid out the folder structure & stubbed out the app.js and a few other JavaScript files.
I’ll now give a brief overview of setting up the data access layer, and then delve into creating the API to return JSON.
Entity Framework Code First
I’ll very quickly go over the steps for creating some basic data that our grid can consume, if you are new to Entity Framework’s code first migrations, then hop over to the following link for a refresher. Also, take a look at the following link. This is a tutorial by Tom Dykstra and I’ll be using the same data to seed the dB but instead of lumping the data access layer within the web project I’ll make a separate data access project.
Add a new class project & name it NgWebApiGrid.Data
– now in your package manager console, type the following “Install-Package EntityFramework
”. This will install nuget package for Entity Framework.
Next, create a folder called models & add the following files Course.cs, Enrollment.cs and Student.cs. The class structure for each of these three files is as follows:
namespace NgWebApiGrid.Data.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
namespace NgWebApiGrid.Data.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
}
namespace NgWebApiGrid.Data.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Add a SchoolContext.cs file to the root of the project. The contents of this file should be as follows:
namespace NgWebApiGrid.Data
{
public class SchoolContext : DbContext
{
public SchoolContext()
: base("SchoolContext")
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
Next, run Enable-Migrations in the package manager console. You should see a new folder Migrations created. The Configuration file should look as follows:
using System.Collections.Generic;
using NgWebApiGrid.Data.Models;
namespace NgWebApiGrid.Data.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration :
DbMigrationsConfiguration<NgWebApiGrid.Data.SchoolContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(NgWebApiGrid.Data.SchoolContext context)
{
var students = new List<Student>
{
new Student{FirstMidName="Carson",
LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
new Student{FirstMidName="Meredith",
LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Arturo",
LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Gytis",
LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Yan",
LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Peggy",
LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
new Student{FirstMidName="Laura",
LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Nino",
LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")},
new Student{FirstMidName="Darson",
LastName="Olivia",EnrollmentDate=DateTime.Parse("2005-09-01")},
new Student{FirstMidName="Cheryl",
LastName="Bruto",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Angus",
LastName="Doritho",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Jeves",
LastName="Baldaros",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Kan",
LastName="Chan",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="David",
LastName="Stosky",
EnrollmentDate=DateTime.Parse("2001-09-01")},
new Student{FirstMidName="Lauda",
LastName="Chris",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Cheeko",
LastName="Madus",EnrollmentDate=DateTime.Parse("2005-09-01")}
};
students.ForEach(s => context.Students.Add(s));
context.SaveChanges();
var courses = new List<Course>
{
new Course{CourseID=1050,Title="Chemistry",Credits=3,},
new Course{CourseID=4022,Title="Microeconomics",Credits=3,},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3,},
new Course{CourseID=1045,Title="Calculus",Credits=4,},
new Course{CourseID=3141,Title="Trigonometry",Credits=4,},
new Course{CourseID=2021,Title="Composition",Credits=3,},
new Course{CourseID=2042,Title="Literature",Credits=4,}
};
courses.ForEach(s => context.Courses.Add(s));
context.SaveChanges();
var enrollments = new List<Enrollment>
{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050,},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
enrollments.ForEach(s => context.Enrollments.Add(s));
context.SaveChanges();
}
}
}
Add the following connection string to the app.config:
<connectionStrings>
<add name="SchoolContext"
connectionString="Data Source=(local);
Initial Catalog=ContosoUniversity1;Integrated Security=SSPI;
" providerName="System.Data.SqlClient"/>
</connectionStrings>
Next, run Add-Migration in the Package Manager console. This will create a dB instance which will have the Student
, Enrollment
and Course
tables populated with the seed data.
Go back to the web project and add a reference to the data project. Right click on the controllers folder and select add and controller option.
Select Web API2 Controller with actions, using Entity Framework.
In the next dialog box, make sure the selections are as follows:
On selecting Add above, a StudentApiController
will be created and this will have the basic CRUD actions.
To the web.config on the web project, add the following connection string:
<add name="SchoolContext" connectionString="Data Source=(local);
Initial Catalog=ContosoUniversity1;Integrated Security=SSPI;"
providerName="System.Data.SqlClient" />
Go ahead and fire-up Fiddler and compose a get request as follows:
On executing this request, you should see JSON data being returned.
Implement Grid in AngularJS
Finally, now we can get into implementing the grid in Angular.
First, let’s add the template to display the list of students in a grid. Each template will be married to a controller, similar to server side frameworks, the difference here is that there is a one to one correspondence between controllers and templates rather than controller actions being matched with a given view as happens with server side MVC.
Add a file to the app/simpleGrid folder and name it students.tpl.html and the code within the template should be like so:
<div class="row top-buffer">
<table class="table table-bordered table-striped table-responsive">
<thead>
<tr>
<th>
</th>
<th>
</th>
<th>
</th>
<th>
Last Name
</th>
<th>
First Name
</th>
<th>
Date of Enrollment
</th>
</tr>
</thead>
<tbody data-ng-repeat="i in data">
<tr>
<td></td>
<td></td>
<td></td>
<td>
<textarea class="form-control"
style="width: 300px;height: 65px" ng-model="i.lastName"></textarea>
</td>
<td>
<textarea class="form-control"
style="width: 300px;height: 65px" ng-model="i.firstMidName"></textarea>
</td>
<td>
<input type="text" class="form-control"
style="width: 150px;height: 65px" ng-model="i.enrollmentDate" />
</td>
</tr>
</tbody>
</table>
</div>
Next, modify the app/SimpleGrid/student.js file to look like this:
angular.module('ngWebApiGrid.student', ['ngRoute'])
.config(["$routeProvider", function ($routeProvider) {
$routeProvider.when("/", {
controller: "studentCtrl",
templateUrl: "app/simpleGrid/students.tpl.html"
});
$routeProvider.otherwise("/");
}])
.factory("dataService", ["$http", "$q", function ($http, $q) {
var _students = [];
var deferred = $q.defer();
var _getStudents = function (options) {
$http.get("api/StudentsApi")
.then(function (result) {
angular.copy(result.data.students, _students);
deferred.resolve();
},
function () {
deferred.reject();
});
return deferred.promise;
};
return {
students:_students,
getStudents: _getStudents,
};
}])
.controller("studentCtrl", ["$scope", "dataService",
function ($scope, dataService) {
$scope.data = dataService.students;
var options = {
};
dataService.getStudents(options)
.then(function() {
},
function() {
});
}])
On the $scope
, we have defined a data object & then implemented a dataService
service that is responsible for making the actual call to the web API. The dataService
implementation follows the Revealing module pattern. Since the calls to the web API are asynchronous in nature when a call to getStudents
is made, a promise is returned.
Now, go to the StudentApiController.cs file and define a new class called StudentContainer
. This class will act as a wrapper class and will contain two properties – a Students
property and then a RecordCount
property; it will become apparent why we do this in a bit when we implement paging. So the StudentApiController
should look as follows:
public StudentContainer GetStudents()
{
var students = db.Students.ToList();
var studentsContainer = new StudentContainer
{ Students = students, RecordCount = students.Count() };
return studentsContainer;
}
....
public class StudentContainer
{
public List<Student> Students { get; set; }
public int RecordCount
{ get; set; }
}
Before you run the application, one other change has to be made to the Register
method of the WebApiConfig
, see the highlighted line below – this ensures that the json serializer returns pascal cased attributes which is the standard coding convention for JavaScript.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SupportedMediaTypes.Add
(new MediaTypeHeaderValue("text/html"));
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
}
}
Run the solution and you should now see a grid with the students
being displayed:
As always, the source code is available at https://github.com/SangeetAgarwal/NgWebApiGrid.
To review the changes/additions I have made in this post, please see the following SHA.
In my next blog post, I describe how to setup server side paging.
<a class="twitter-follow-button" data-link-color="#2390bb" data-show-count="false" data-show-screen-name="false" data-text-color="#222222" href="http://twitter.com/sangymohan">Follow @sangymohan</a>