As per our business requirements, we need to apply a filter, paging, and sorting to our FlixOne inventory application. First, let's start implementing the sorting. To do so, I've created a project and put this project in the FlixOneWebExtended folder. Start Visual Studio and open the FlixOne solution. We will apply to sort to our product listing sheet for these columns: Category, productName, Description, and Price. Please note that we will not be using any external component for sorting, but we will create our own login.
Open the Solution Explorer, and open ProductController, which is available in the Controllers folder. Add the [FromQuery]Sort sort parameter to the Index method. Please note that the [FromQuery] attribute indicates that this parameter is a query parameter. We will use this parameter to maintain our sorting order.
The following code shows the Sort class:
public class Sort
{
public SortOrder Order { get; set; } = SortOrder.A;
public string ColName { get; set; }
public ColumnType ColType { get; set; } = ColumnType.Text;
}
The Sort class contains three public properties as detailed here:
- Order: Indicates the sorting order. The SortOrder is an enum defined as public enum SortOrder { D, A, N }.
- ColName: Indicates the column name.
- ColType: Indicates the type of a column; ColumnType is an enum defined as public enum ColumnType { Text, Date, Number }.
Open the IInventoryRepositry interface, and add the IEnumerable<Product> GetProducts(Sort sort) method. This method is responsible for sorting the results. Please note that we are going to use LINQ queries to apply sorting. Implement this InventoryRepository class method and add the following code:
public IEnumerable<Product> GetProducts(Sort sort)
{
if(sort.ColName == null)
sort.ColName = "";
switch (sort.ColName.ToLower())
{
case "categoryname":
{
var products = sort.Order == SortOrder.A
? ListProducts().OrderBy(x => x.Category.Name)
: ListProducts().OrderByDescending(x => x.Category.Name);
return PDiscounts(products);
}
The following code is handling the case when sort.ColName is productname:
case "productname":
{
var products = sort.Order == SortOrder.A
? ListProducts().OrderBy(x => x.Name)
: ListProducts().OrderByDescending(x => x.Name);
return PDiscounts(products);
}
The following code is handling the case when sort.ColName is productprice:
case "productprice":
{
var products = sort.Order == SortOrder.A
? ListProducts().OrderBy(x => x.Price)
: ListProducts().OrderByDescending(x => x.Price);
return PDiscounts(products);
}
default:
return PDiscounts(ListProducts().OrderBy(x => x.Name));
}
}
In the previous code, we set the value of the sort parameter as blank if it contains a null value, and then we process it by using switch..case in sort.ColName.ToLower().
The following is our ListProducts() method that gives us the result of the IIncludeIQuerable<Product,Category> type:
private IIncludableQueryable<Product, Category> ListProducts() =>
_inventoryContext.Products.Include(c => c.Category);
The preceding code simply gives us Products by including Categories for each product. The sorting order will come from our user, so we need to modify our Index.cshtml page. We also need to add an anchor tag to the header columns of the table. For this, consider the following code:
<thead>
<tr>
<th>
@Html.ActionLink(Html.DisplayNameFor(model => model.CategoryName), "Index", new Sort { ColName = "CategoryName", ColType = ColumnType.Text, Order = SortOrder.A })
</th>
<th>
@Html.ActionLink(Html.DisplayNameFor(model => model.ProductName), "Index", new Sort { ColName = "ProductName", ColType = ColumnType.Text, Order = SortOrder.A })
</th>
<th>
@Html.ActionLink(Html.DisplayNameFor(model => model.ProductDescription), "Index", new Sort { ColName = "ProductDescription", ColType = ColumnType.Text, Order = SortOrder.A })
</th>
</tr>
</thead>
The preceding code show the header columns of the table; new Sort { ColName = "ProductName", ColType = ColumnType.Text, Order = SortOrder.A } is the main way we are implementing SorOrder.
Run the application and you will see the following snapshot of the Product Listing page with the sorting feature:
Now, open the Index.cshtml page, and add the following code to the page:
@using (Html.BeginForm())
{
<p>
Search by: @Html.TextBox("searchTerm")
<input type="submit" value="Search" class="btn-sm btn-success" />
</p>
}
In the preceding code, we are adding a textbox under Form. Here, the user inputs the data/value, and this data submits to the server as soon as the user clicks the submit button. At the server side, the filtered data will returned back and show the product listing. After the implementation of the preceding code, our Product Listing page will look like this:
Go to the Index method in ProductController and change the parameters. Now the Index method looks like this:
public IActionResult Index([FromQuery]Sort sort, string searchTerm)
{
var products = _repositry.GetProducts(sort, searchTerm);
return View(products.ToProductvm());
}
Similarly, we need to update the method parameters of GetProducts() in InventoryRepository and InventoryRepository. The following is the code for the InventoryRepository class:
private IEnumerable<Product> ListProducts(string searchTerm = "")
{
var includableQueryable = _inventoryContext.Products.Include(c => c.Category).ToList();
if (!string.IsNullOrEmpty(searchTerm))
{
includableQueryable = includableQueryable.Where(x =>
x.Name.Contains(searchTerm) || x.Description.Contains(searchTerm) ||
x.Category.Name.Contains(searchTerm)).ToList();
}
return includableQueryable;
}
Now run the project by pressing F5 from Visual Studio and navigating to the filter/search option in Product Listing. For this, see this snapshot:
After entering your search term, click on the Search button, and this will give you the results, as shown in the following snapshot:
In the preceding Product Listing screenshot, we are filtering our Product records with searchTerm mango, and it produces single results, as shown in the previous snapshot. There is one issue in this approach for searching data: add fruit as a search term, and see what will happen. It will produce zero results. This is demonstrated in the following snapshot:
We do not get any result, which means our search is not working when we are putting searchTerm in lowercase. This means our search is case-sensitive. We need to change our code to get it started.
Here is our modified code:
var includableQueryable = _inventoryContext.Products.Include(c => c.Category).ToList();
if (!string.IsNullOrEmpty(searchTerm))
{
includableQueryable = includableQueryable.Where(x =>
x.Name.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase) ||
x.Description.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase) ||
x.Category.Name.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)).ToList();
}
We are ignoring the case to make our search case-insensitive. We used StringComparison.InvariantCultureIgnoreCase and ignored the case. Now our search will work with either capital or lowercase letters. The following is the snapshot that produces results using lowercase fruit:
In a previous discussion during the FlixOne app extension, we applied Sort and Filter; now we need to add paging. To do so, we have added a new class named, PagedList as follows:
public class PagedList<T> : List<T>
{
public PagedList(List<T> list, int totalRecords, int currentPage, int recordPerPage)
{
CurrentPage = currentPage;
TotalPages = (int) Math.Ceiling(totalRecords / (double) recordPerPage);
AddRange(list);
}
}
Now, change the parameters of the Index method of ProductController as follows:
public IActionResult Index([FromQuery] Sort sort, string searchTerm,
string currentSearchTerm,
int? pagenumber,
int? pagesize)
Add the following code to the Index.cshtml page:
@{
var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.HasNextPage ? "disabled" : "";
}
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.CurrentPage - 1)"
asp-route-currentFilter="@ViewData["currentSearchTerm"]"
class="btn btn-sm btn-success @prevDisabled">
Previous
</a>
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.CurrentPage + 1)"
asp-route-currentFilter="@ViewData["currentSearchTerm"]"
class="btn btn-sm btn-success @nextDisabled">
Next
</a>
The preceding code makes it possible to move our screen to the next or the previous page. Our final screen will look like this:
In this section, we have discussed and extended the features of our FlixOne application by implementing Sorting, Paging, and Filter. The aim of this section was to give you hands-on experience with a working application. We have coded our application in such a way that it will directly meet real-world applications. With the preceding enhancement, our application is now capable of giving a product listing that can be sorted, paginated, and filtered.