This post will cover implementing the paging, filtering and sorting server-side. By implementing these server-side features, we will reduce the data fetched from the database, transmitted with the API request and stored in memory, improving the application’s performance.
To follow along, check out the code and switch to the Implemented-Sorting branch, where we stopped in the last post.
To retrieve only the necessary data for the current page or viewport and apply any server-side sorting or filtering rules, you can use the ItemsProvider parameter. This parameter accepts a callback that matches the GridItemsProvider<TGridItem> delegate type, where TGridItem is the data type displayed in the grid.
The callback will receive a GridItemsProviderRequest<TGridItem> parameter, which specifies the start index, maximum row count, and sort order of the data to return. In addition to returning the matching items, you must also return a totalItemCount so that paging or virtualisation can function correctly.
The GridItemsProvider’s job is to convert QuickGrid’s GridItemsProviderRequest into a query against an arbitrary data source. We need to translate query parameters into the URL format supported by the external JSON API. The API does not yet support paging or filtering, so we will also build that functionality.
We’ll also need to create a custom DTO that contains both the requested customers and the total customer count, as we won’t be able to obtain the total from the returned customers.
Create a CustomersWrapper class as follows:
public class CustomersWrapper
{
public int TotalCustomerCount { get; set; }
public List<Customer> Customers { get; set; } = new List<Customer>();
}
Next, let’s update the controller to accept query parameters for skip and limit to control paging, and nameFilter, emailAddressFilter, userNameFilter for filtering.
public async Task<CustomersWrapper> Get([FromQuery]int skip, [FromQuery] int limit, [FromQuery] string? nameFilter, [FromQuery]string? emailAddressFilter, [FromQuery]string? userNameFilter)
Next, we’ll use Entity Framework to control what data gets pulled from the database. The following will filter the data, apply paging and then return the data using the CustomersWrapper class.
var totalCustomerCount = await _appDbContext.Customers.CountAsync();
var customersQuery = _appDbContext.Customers.AsQueryable();
if (!string.IsNullOrEmpty(nameFilter))
{
customersQuery = customersQuery.Where(c => c.FirstName.Contains(nameFilter)
|| c.LastName.Contains(nameFilter));
}
if (!string.IsNullOrEmpty(emailAddressFilter))
{
customersQuery = customersQuery.Where(c => c.Email.Contains(emailAddressFilter));
}
if (!string.IsNullOrEmpty(userNameFilter))
{
customersQuery = customersQuery.Where(c => c.UserName.Contains(userNameFilter));
}
var customers = await customersQuery.Skip(skip)
.Take(limit)
.Select(c => new Customer
{
Name = c.FirstName + " " + c.LastName,
EmailAddress = c.Email,
UserName = c.UserName,
Avatar = c.Avatar
}).ToListAsync();
return new CustomersWrapper
{
TotalCustomerCount = totalCustomerCount,
Customers = customers
};
Next, we must work on the FetchData.razor file. Most of the code in the @code block will need to be changed. Change the IQueryable fields to be instead a reference to the QuickGrid component and GridItemsProvider<Customer>.
private QuickGrid<Customer?>? customersGrid;
private GridItemsProvider<Customer>? customersProvider;
private PaginationState paginationState = new PaginationState();
Create properties with backing fields for the filters, which, when set, call the RefreshDataAsync() method on the customersGrid.
string? _nameFilter;
string? nameFilter
{
get
{
return _nameFilter;
}
set
{
_nameFilter = value;
customersGrid?.RefreshDataAsync();
}
}
string? _emailAddressFilter;
string? emailAddressFilter
{
get
{
return _emailAddressFilter;
}
set
{
_emailAddressFilter = value;
customersGrid?.RefreshDataAsync();
}
}
string? _userNameFilter;
string? userNameFilter
{
get
{
return _userNameFilter;
}
set
{
_userNameFilter = value;
customersGrid?.RefreshDataAsync();
}
}
Change the OnInitializedAsync method to the non-async override and replace the contents with the following.
protected override void OnInitialized()
{
paginationState.TotalItemCountChanged += (sender, eventArgs) => StateHasChanged();
customersProvider = async req =>
{
var url = NavigationManager.GetUriWithQueryParameters("https://localhost:7111/Customer", new Dictionary<string, object?>
{
{ "skip", req.StartIndex },
{ "limit", req.Count },
{ "nameFilter", nameFilter },
{ "emailAddressFilter", emailAddressFilter },
{ "userNameFilter", userNameFilter }
});
var response = await Http.GetFromJsonAsync<CustomersWrapper>(url, req.CancellationToken);
return GridItemsProviderResult.From(
items: response!.Customers,
totalItemCount: response!.TotalCustomerCount);
};
}
We set an event handler to the paginationState.TotalItemCountChanged’s property so that the UI can update after fetching items from the API for the first time. After that, we set the customer’s provider up to make an API call, providing paging and filtering parameters.
Finally, run the API + Web.UI projects together, and you should have the following result:
The code up to here can be found on the Implemented-API-Paging-Sorting-Filtering branch.
That’s it. I hope this post series has helped you work with the QuickGrid component.