AngularJS + ASP.NET Web API 2 - Server-side Sorting, Searching and Paging

09 May 2014

An example of how to setup an html table with server-side paging, searching and sorting with AngularJS and ASP.NET WebAPI (and a little help from bootstrap).

Paging, searching and sorting server-side is a must if your working with large collections of data, if you've attempted to process thousands or millions of records in the browser then you know exactly how painful it can be, practically grinding the browser to a halt! Moving all this heavy lifting to the server will make your application much more responsive and drastically reduce the amount of bandwidth you're using.

AngularJS View

The view contains three main elements, a search form, a table of users and a pagination control. All of the searching, sorting and paging parameters are encapsulated by a pagingInfo object.

The pagination directive is from the UI Bootstrap library which contains all the bootstrap components written in pure AngularJS (http://angular-ui.github.io/bootstrap/).

The css styling is from the bootstrap 3 framework.

<form class="form-inline" ng-submit="search()" role="form">
    <div class="form-group">
        <input type="text" class="form-control input-sm" ng-model="pagingInfo.search" placeholder="Search...">
        <button type="submit" class="btn btn-info btn-sm"><strong>Search</strong></button>
    </div>
</form>

<table class="table table-striped table-bordered table-hover table-condensed">
    <thead>
        <tr>
            <th><a href="" ng-click="sort('FirstName')">First Name</a></th>
            <th><a href="" ng-click="sort('LastName')">Last Name</a></th>
            <th><a href="" ng-click="sort('Username')">Username</a></th>
            <th><a href="" ng-click="sort('Email')">Email</a></th>
            <th><a href="" ng-click="sort('Role')">Role</a></th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="user in users">
            <td>{{user.FirstName}}</td>
            <td>{{user.LastName}}</td>
            <td>{{user.Username}}</td>
            <td>{{user.Email}}</td>
            <td>{{user.Role}}</td>
        </tr>
    </tbody>
</table>

<pagination page="pagingInfo.page"
            total-items="pagingInfo.totalItems"
            items-per-page="pagingInfo.itemsPerPage"
            on-select-page="selectPage(page)"
            max-size="10"
            rotate="false"
            boundary-links="true"></pagination>

 

AngularJS Controller

The controller contains all of the functions for paging, sorting and searching the table of users, it sets the default values on the pagingInfo object and loads the users with an AngularJS factory called UserService.

'use strict';

angular.module('Users')

.controller('Users.HomeController',
    ['$scope', 'UserService',
    function ($scope, UserService) {
        $scope.pagingInfo = {
            page: 1,
            itemsPerPage: 30,
            sortBy: 'FirstName',
            reverse: false,
            search: '',
            totalItems: 0
        };
        
        $scope.search = function () {
            $scope.pagingInfo.page = 1;
            loadUsers();
        };

        $scope.sort = function (sortBy) {
            if (sortBy === $scope.pagingInfo.sortBy) {
                $scope.pagingInfo.reverse = !$scope.pagingInfo.reverse;
            } else {
                $scope.pagingInfo.sortBy = sortBy;
                $scope.pagingInfo.reverse = false;
            }
            $scope.pagingInfo.page = 1;
            loadUsers();
        };

        $scope.selectPage = function (page) {
            $scope.pagingInfo.page = page;
            loadUsers();
        };

        function loadUsers() {
            $scope.users = null;
            UserService.GetUsers($scope.pagingInfo).success(function (data) {
                $scope.users = data.data;
                $scope.pagingInfo.totalItems = data.count;
            });
        }

        // initial table load
        loadUsers();
    }]);

 

AngularJS Service

The UserService makes an HTTP GET call to the ASP.NET Web API to get a list of users, passing the pagingInfo object as the parameters. The pagingInfo object gets converted into querystring parameters automatically by AngularJS.

'use strict';

angular.module('Users')

.factory('UserService', ['$http', 
    function ($http) {
        var service = {};

        service.GetUsers = function (pagingInfo) {
            return $http.get('/api/users', { params: pagingInfo });
        };

        return service;
    }]);

 

ASP.NET Web API 2 Controller

The Web API controller takes the parameters sent by the AngularJS service and returns a collection of users in JSON format.

I've hardcoded a dummy list of users for the example, but this would be replaced by a call to an ORM or some form of data repository in the real world, I recommend NHibernate.

The sorting functionality is done using the Dynamic LINQ Library, which there is surprisingly little about online but I've found extremely useful, you can install it via NuGet with the command Install-Package System.Linq.Dynamic.

public class UsersController : ApiController
{
    public IHttpActionResult Get(
        int page = 1, 
        int itemsPerPage = 30, 
        string sortBy = "FirstName", 
        bool reverse = false,
        string search = null)
    {
        // create list of 100 dumy users, replace
        // with call to repo in real app
        var users = Enumerable.Range(1, 100)
                .Select(x => new User
                {
                    Id = x,
                    FirstName = "FirstName" + x,
                    LastName = "LastName" + x,
                    Username = "Username" + x,
                    Email = "Email" + x,
                    Role = "Role" + x
                }).AsQueryable();


        // searching
        if (!string.IsNullOrWhiteSpace(search))
        {
            search = search.ToLower();
            users = users.Where(x =>
                x.FirstName.ToLower().Contains(search) ||
                x.LastName.ToLower().Contains(search) ||
                x.Username.ToLower().Contains(search) ||
                x.Email.ToLower().Contains(search) ||
                x.Role.ToLower().Contains(search));
        }

        // sorting (done with the System.Linq.Dynamic library available on NuGet)
        users = users.OrderBy(sortBy + (reverse ? " descending" : ""));

        // paging
        var usersPaged = users.Skip((page - 1) * itemsPerPage).Take(itemsPerPage);

        // json result
        var json = new
        {
            count = users.Count(),
            data = usersPaged.Select(x => new
            {
                Id = x.Id,
                FirstName = x.FirstName,
                LastName = x.LastName,
                Username = x.Username,
                Email = x.Email,
                Role = x.Role
            })
        };

        return Ok(json);
    }
}