Pagination is a fundamental concept in web development, essential for managing and presenting large datasets, in a user-friendly manner. Whether you are building an e-commerce website or a blog. Implementing an effective pagination ensures a smooth user experience. In this article, we will explore implementation of pagination in dotnet API .This API would query and return a list of hotels and restaurants with their reviews.
Why Use Pagination?
Considering we might be having a large number of hotels and restaurants, so when a client makes a query, the controller would ideally present all the listed hotels from the server. Trust me that can't be good considering that might slow the server and the user might not even have to go through the whole list, hence we use pagination to present a smaller chunk of the list at each given time and should the client want some more they should request for a new set .
So in this case we might populate the first page with 10 hotels to chose from then from there the next page and so forth. Worth to Note also is the case of a USSD application where some information would spill to the next page, so the right practice would be to use pagination for items that would fit the screen and have the rest in a separate menu.
While creating Pagination a great practice would be to create a paginated response class that would contain the information about the page number, number of data to be included per page and the data itself so in essence it can act as a DTO.
Implementing Pagination
Here's a step-by-step guide on how to implement pagination in your web API, using a .NET-based example:
After Project setup including connecting to a database Go ahead and create a model Hotel.cs,among other models.An example is seen below.
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace TravelAPI.Models
{
public class Hotel
{
[Required]
public int Id { get; set; }
public string Name { get; set; }
public string Slug { get; set; }
public string Description { get; set; }
public string Keywords { get; set; }
public string PhoneNumber { get; set; }
public string RestaurantEmail { get; set; }
public string Opens { get; set; }
public string Closes { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string Phone { get; set; }
public decimal Price { get; set; }
public string ReserveUrl { get; set; }
public string ImageUrl { get; set; }
public string ReservePhoneNumberUrl { get; set; }
[ForeignKey("HotelId")]
public List<HotelReview> hotelReviews { get; set; }
public DateTime createdAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
}
Querying such a model in a normal Get controller method would give you all the hotels that are in the database ,that is in case no other parameters are setup ,this can be see in the example below.
// GET: api/hotelReviews
[HttpGet]
public async Task<IActionResult> Index()
{
var hotels = await
_context.hotels.ToListAsync();
if (hotels != null)
{
return Ok(hotels);
}
else
{
return NotFound("Hotels Not Found");
}
}
the IActionResult would get all the hotels, now to implement pagination for such a route, we would have to create a new model class to store the the response from the querying hotels which would then be displayed. We would also be required to setup parameters that are passed during querying to determine the number of hotels we would require displayed. In my case I implemented another classes with default parameters and a parameter less class with default number of hotels per page
So here is the code to our paginated response class.
namespace PublicTravelApi.Models
{
public class PagenatedResponse<T>
{
public int count { get; set; }
public int per_page { get; set;}
public int current_page { get; set; }
public T items { get; set; }
public PagenatedResponse(int count, int per_page, int
current_page, T items)
{
this.count = count;
this.per_page = per_page;
this.current_page = current_page;
this.items = items;
}
}
}
So there it is a new paginated response that would contains the total count of hotels ,the current page and hotels per page information.
The next step would be to have a class defining our filters that we would use to define the page as implemented in this class.
namespace PublicTravelApi.Models
{
public class PaginationFilter
{
public int per_page { get; set; }
public int current_page { get; set; }
public PaginationFilter()
{
this.per_page = 2;
this.current_page = 1;
}
public PaginationFilter(int per_page,int current_page)
{
this.per_page = per_page > 10? 10 : per_page;
this.current_page = current_page <1 ? 1 :
current_page;
}
}
}
To manage our views we are setting two parameters: per_page and current_page, with the first page or in case a client doesn't provide any parameters it's going to be two hotels on page 1.Should the client give parameters then the number of hotels should not go beyond 10.If you not conversant with this line this.per_page = per_page > 10? 10 : per_page;, this is a shorthand if statement in this case what it means is, if the client request hotels per_page is greater than 10 set per_page to 10 else if less then set it to the provided per_page.
With those parameters set we now have to edit our Get controller so that the we only get a response with our filters. Here is the new hotel controller class.
[HttpGet]
public async Task<ActionResult <IEnumerable<Hotel>>> GetHotels([FromQuery]PaginationFilter filter)
{
var validPageFilter = new PaginationFilter(filter.per_page, filter.current_page);
var hotels = await _context.hotels.Include(x=>x.hotelReviews)
.Skip((validPageFilter.current_page-1)*validPageFilter.per_page)
.Take(validPageFilter.per_page)
.ToListAsync();
var totalHotels = await _context.hotels.CountAsync();
return Ok(new PagenatedResponse<List<Hotel>>(totalHotels,validPageFilter.per_page,validPageFilter.current_page,hotels));
if (hotels != null)
{
return Ok(hotels); // Return a 200 OK response with the JSON data
}
else
{
return NotFound("No hotels found"); // Return a 404 Not Found response
}
}
GET /api/hotels?per_page=10¤t_page=2 that is an example of a request from the client. The addition w added in our control including binding parameters from the query which is from the client. This creates anew instance of our filter which we use in retrieving our hotels. In this line :
var hotels = await _context.hotels.Include(x=>x.hotelReviews)
.Skip((validPageFilter.current_page-1)*validPageFilter.per_page)
.Take(validPageFilter.per_page)
.ToListAsync();
we are skipping the current page if a user want to navigate to page 3,the current page would 2 the what we skip is (2-1)*2 where we are multiplying by per page. what we take for display would be the next two hotels after the first four which were skipped in this case it would the fifth and the sixth.
I hope I explained everything clearly .
Infinite Scroll vs. Pagination
While pagination is a tried-and-true method, some websites opt for infinite scrolling. Infinite scrolling continuously loads new content as users scroll down. While this approach has its advantages, such as a seamless browsing experience, it also has potential downsides, such as performance issues and challenges for users trying to find specific content.
In conclusion, pagination is a crucial design pattern for web developers to master. It plays a pivotal role in enhancing user experience, optimizing performance, and making your website or application more SEO-friendly. By understanding the principles of pagination and effectively implementing it, you can ensure that users can easily access and navigate through your content, no matter how extensive it may be.
Top comments (1)
I would appreciate any comments