Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

AngularJS + ASP.NET Web API: Building a Simple Grid in AngularJS with Server-side Paging, Sorting, Searching (Part 2)

0.00/5 (No votes)
26 Feb 2015CPOL4 min read 11.5K  
AngularJS + ASP.NET Web API: Building a Simple Grid in AngularJS with Server-side Paging, sorting, searching (Part 2)

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:

C#
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; }
    }
}
C#
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; }
    }
}
C#
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:

C#
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:

C#
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:

C#
<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.

NgWebApiGrid13

Select Web API2 Controller with actions, using Entity Framework.

NgWebApiGrid14

In the next dialog box, make sure the selections are as follows:

NgWebApiGrid15

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:

XML
<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:

NgWebApiGrid16

On executing this request, you should see JSON data being returned.

NgWebApiGrid17

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:

HTML
<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:

JavaScript
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:

C#
 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.

C#
public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            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:

NgWebApiGrid18

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>

Image 7 Image 8

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)