This is the seventh part of Building ASP.Net Web API RESTful Service Series. The topics we’ll cover are:
- Building the Database Model using Entity Framework Code First – Part 1.
- Applying the Repository Pattern for the Data Access Layer – Part 2.
- Getting started with ASP.Net Web API - Part 3.
- Implement Model Factory, Dependency Injection and Configuring Formatters - Part 4.
- Implement HTTP actions POST, PUT, and DELETE In Web API - Part 5.
- Implement Resources Association - Part 6.
- Implement Resources Pagination - Part 7 (This Post).
- Securing Web API – Part 8. (Coming Soon)
- Preparing Web API for Versioning – Part 9. (Coming Soon)
- Different techniques to Implement Versioning – Part 10. (Coming Soon)
- Cashing resources using eTags – Part 11. (Coming Soon)
Implement Resources Pagination
In this post we’ll discuss the different ways to implement results pagination, we’ll implement manual pagination then format the response in two different ways (having pagination meta-data in an envelope, and in pagination header).
It is well known that overwhelming your server with a query which returns hundreds of thousand of records is a bad thing, when we are designing an API, we should consider returning the results of our GET methods in paginated way, i.e. providing 10 results on each request, and giving the API consumer the ability to navigate through results, specify the size of the page, and which page he wants.
Manual Pagination and Envelopes
We’ll modify the “CoursesController” to use pagination instead of returning the whole courses at once.
Let’s see the code below:
public Object Get(int page = 0, int pageSize = 10)
{
IQueryable<Course> query;
query = TheRepository.GetAllCourses().OrderBy(c => c.CourseSubject.Id);
var totalCount = query.Count();
var totalPages = (int)Math.Ceiling((double)totalCount / pageSize);
var urlHelper = new UrlHelper(Request);
var prevLink = page > 0 ? urlHelper.Link("Courses", new { page = page - 1 }) : "";
var nextLink = page < totalPages - 1 ? urlHelper.Link("Courses", new { page = page + 1 }) : "";
var results = query
.Skip(pageSize * page)
.Take(pageSize)
.ToList()
.Select(s => TheModelFactory.Create(s));
return new
{
TotalCount = totalCount,
TotalPages = totalPages,
PrevPageLink = prevLink,
NextPageLink = nextLink,
Results = results
};
}
What we’ve done here is simple, we’ve introduced the below to “CoursesController”
- Added two new optional parameters to the GET method with default values, those optional parameters are translated to query string values, i.e. if we want to request the second page of courses our GET request will be on the form: http://localhost:{your_port}/api/courses/?page=1 Notice we didn’t specify the pageSize parameter and it took the default values 10. Sample of response will be on the form below:
{
"totalCount": 33,
"totalPages": 4,
"prevPageLink": "http://localhost:8323/api/courses?page=0&pageSize=10",
"nextPageLink": "http://localhost:8323/api/courses?page=2&pageSize=10",
"results": [ /* Array containts the results*/ ]
}
- The method “GetAllCourses” in our Repository returns “IQueryable” response, which is perfect because till this moment the query is represented in memory and didn’t execute against SQL server, so paging and order by for query are executing correctly.
- We’re using envelope to wrap our response, this envelope contains pagination meta-data inside the JSON response such as: totalCount, totalPages, prevPageLink, nextPageLink. It is important to return the total records count and total pages so API consumer will be able to bind results and apply pagination on grid easily.
Returning the pagination meta-data in the response body is a common technique, there is nothing wrong about it as everything is visible for the developer, the draw back of this approach is that API consumer will dig into the response to extract data he was originally asking for and maybe ignoring all the pagination meta data we returned if he do not need to use it. So the other cleaner way to return pagination meta-data is to include them in response header, so we’ll keep the response body for the results only and we’ll add new header called “X-Pagination” which contains all pagination meta-data.
Manual Pagination and Pagination Headers
We’ll modify “StudentsController” to use headers to return pagination meta-data, in this approach API consumer can use this header if he is interested in the pagination meta-data, other wise he can just ignore this header and read the results of the query directly from response body.
Applying this is fairly simple, the pagination technique we used in “CoursesControrler” will be used the same here, except for returning the pagination meta-data in new header. Take a look on the code below:
public IEnumerable<StudentBaseModel> Get(int page = 0, int pageSize = 10)
{
IQueryable<Student> query;
query = TheRepository.GetAllStudentsWithEnrollments().OrderBy(c => c.LastName);
var totalCount = query.Count();
var totalPages = (int)Math.Ceiling((double)totalCount / pageSize);
var urlHelper = new UrlHelper(Request);
var prevLink = page > 0 ? urlHelper.Link("Students", new { page = page - 1, pageSize = pageSize }) : "";
var nextLink = page < totalPages - 1 ? urlHelper.Link("Students", new { page = page + 1, pageSize = pageSize }) : "";
var paginationHeader = new
{
TotalCount = totalCount,
TotalPages = totalPages,
PrevPageLink = prevLink,
NextPageLink = nextLink
};
System.Web.HttpContext.Current.Response.Headers.Add("X-Pagination",
Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader));
var results = query
.Skip(pageSize * page)
.Take(pageSize)
.ToList()
.Select(s => TheModelFactory.CreateSummary(s));
return results;
}
Notice how we added a new header to the response collection headers which contains a serialized JSON object containing all pagination meta-data.
In the next post we’ll talk briefly about web API security and how we can implement Basic authentication.
Source code is available on GitHub.