Quick start: ASP.Net Core 3.1, Entity Framework Core, CQRS, React JS Series — Part 5: Pagination, Filtering, Searching and Sorting

Ali Süleyman TOPUZ
.Net Programming
Published in
4 min readDec 29, 2020
Well organized pieces make it understandable and readable!

In this content; Pagination, filtering, searching and sorting will be implemented through queries.

The parent content of this series: Quick start: ASP.Net Core 3.1, Entity Framework Core, CQRS, React JS Series

Outline

  • Github feature branch
  • Parameter Definitons (paging, filtering, search, sorting)
  • What comes from Pagination?
  • PagedList definition
  • Sort Helper
  • Using ISortHelper
  • Searching on query and items
  • Use it in Repository
  • Map parameters DTO and send your object as query
  • Response

Github feature branch

Parameter Definitons (paging, filtering, search, sorting)

For a paging and sorting, app needs values to being used as criteria.

public abstract class QueryStringParameters
{
const int maxPageSize = 50;
public int PageNumber { get; set; } = 1;
private int _pageSize = 10;
public int PageSize
{
get
{
return _pageSize;
}
set
{
_pageSize = (value > maxPageSize) ? maxPageSize : value;
}
}
public string OrderBy { get; set; }

}

For Product specific domain:

public class ProductParameters : QueryStringParameters
{
public ProductParameters()
{
OrderBy = "ProductRegisterDate desc";
}

public string Name { get; set; }
}

What comes from Pagination?

The answer brings paged data for sure. Besides, to be able to manage this paging mechanism, Size of page and total count are important, too.

public int CurrentPage { get; private set; }
public int TotalPages { get; private set; }
public int PageSize { get; private set; }
public int TotalCount { get; private set; }
public bool HasPrevious => CurrentPage > 1;
public bool HasNext => CurrentPage < TotalPages;

To be able to use PagedList as follows:

return await PagedList<Product>.ToPagedList(sorderProducts, productParameters.PageNumber, productParameters.PageSize);

There is PagedList class and its extension method:

public class PagedList<T> : List<T>
{
public int CurrentPage { get; private set; }
public int TotalPages { get; private set; }
public int PageSize { get; private set; }
public int TotalCount { get; private set; }
public bool HasPrevious => CurrentPage > 1;
public bool HasNext => CurrentPage < TotalPages;
public PagedList(List<T> items, int count, int pageNumber, int pageSize)
{
TotalCount = count;
PageSize = pageSize;
CurrentPage = pageNumber;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
AddRange(items);
}

public static async Task<PagedList<T>> ToPagedList(IQueryable<T> source, int pageNumber, int pageSize)
{
var count = source.Count();
var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
return new PagedList<T>(items, count, pageNumber, pageSize);
}
}

Sort Helper

As deferred execution, Sorthelper will apply the sort action on IQueryable collection:

public interface ISortHelper<T>
{
IQueryable<T> ApplySort(IQueryable<T> entities, string orderByQueryString);
}

Implementation:

namespace Domain.Infrastructure.EF.Helpers
{
public class SortHelper<T> : ISortHelper<T>
{
public IQueryable<T> ApplySort(IQueryable<T> entities, string orderByQueryString)
{
if (!entities.Any())
return entities;
if (string.IsNullOrWhiteSpace(orderByQueryString))
{
return entities;
}
var orderParams = orderByQueryString.Trim().Split(',');
var propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
var orderQueryBuilder = new StringBuilder();
foreach (var param in orderParams)
{
if (string.IsNullOrWhiteSpace(param))
continue;
var propertyFromQueryName = param.Split(" ")[0];
var objectProperty = propertyInfos.FirstOrDefault(pi => pi.Name.Equals(propertyFromQueryName, StringComparison.InvariantCultureIgnoreCase));
if (objectProperty == null)
continue;
var sortingOrder = param.EndsWith(" desc") ? "descending" : "ascending";
orderQueryBuilder.Append($"{objectProperty.Name} {sortingOrder}, ");
}
var orderQuery = orderQueryBuilder.ToString().TrimEnd(',', ' ');
return entities.OrderBy(orderQuery);
}
}
}

As a generic function, basically it determines column name and sort option and build a orderquery.

Using ISortHelper

Inject:

private ISortHelper<Product> _productSortHelper;

public async Task<PagedList<Product>> GetProductsAsync(ProductParameters productParameters)
{
var products = FindAll();

SearchByName(ref products, productParameters.Name);

var sorderProducts = _productSortHelper.ApplySort(products, productParameters.OrderBy);

return await PagedList<Product>.ToPagedList(sorderProducts, productParameters.PageNumber, productParameters.PageSize);
}

Searching

ProductParameters already includes Name field for search purposes.

private void SearchByName(ref IQueryable<Product> products, string productName)
{
if (!products.Any() || string.IsNullOrWhiteSpace(productName))
return;

products = products.Where(o => o.Name.ToLower().Contains(productName.Trim().ToLower()));
}

Map parameters DTO and send your object as query

Created DTO object below:

public class ProductParametersInfo : IValidatableDto
{
public ProductParametersInfo()
{
OrderBy = "ProductRegisterDate";
PageNumber = 1;
PageSize = 10;
}
public int PageNumber { get; set; }
public int PageSize { get; set; }
public string Name { get; set; }
public string OrderBy { get; set; }
}

And mapping with ProductParameters:

public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<ProductParameters, ProductParametersInfo>().ReverseMap();
}
}

In Specific API, pass your object as follows:

[HttpGet(Name = "get-all-products")]
public async Task<IActionResult> GetAllProducts([FromQuery] ProductParametersInfo productParameters)
{
var productParametersEntity = _mapper.Map<ProductParameters>(productParameters);
var products = await _mediator.Send(new GetAllProductsQuery() { Parameters = productParametersEntity });
Response.Headers.Add("X-Pagination", products.GetMetadata());
var productsResult = _mapper.Map<IEnumerable<ProductDto>>(products);
return Ok(productsResult);
}

Response

X-Pagination
Body of response

Conclusion

Pagination, searching and sorting implementations are demonstrated within implementation level.

--

--

Ali Süleyman TOPUZ
.Net Programming

Software Engineering and Development Professional. Writes about software development & tech. 📍🇹🇷