A practical guide to building an Asp.Net 8 MVC application that uses jQuery component DataTables.net. This is a continuation of article Part8.
1 ASP.NET8 using jQuery DataTables.net
I was evaluating the jQuery DataTables.net component [1] for usage in ASP.NET8 projects and created several prototype (proof-of-concept) applications that are presented in these articles.
1.1 Articles in this series
Articles in this series are:
- ASP.NET8 using DataTables.net – Part1 – Foundation
- ASP.NET8 using DataTables.net – Part2 – Action buttons
- ASP.NET8 using DataTables.net – Part3 – State saving
- ASP.NET8 using DataTables.net – Part4 – Multilingual
- ASP.NET8 using DataTables.net – Part5 – Passing additional parameters in AJAX
- ASP.NET8 using DataTables.net – Part6 – Returning additional parameters in AJAX
- ASP.NET8 using DataTables.net – Part7 – Buttons regular
- ASP.NET8 using DataTables.net – Part8 – Select rows
- ASP.NET8 using DataTables.net – Part9 – Advanced Filters
2 Final result
The goal of this article is to create a proof-of-concept application that demos the DataTables.net component with the use of a custom advanced filter. Let us present the result of this article.
In this example, we disabled the DataTables.net component’s default filter in the upper right corner and created a separate custom form advanced filter. The default filter (search) box was simple and applied the same value for all columns. Often in professional usage one needs more elaborate filtering ability.
I looked very carefully at the site [1] at all the extensions and plugins that the DataTables.net component offers for advanced filtering. But I didn’t like anything that was there, nothing was professional enough for my users. So, I decided to do some coding in ASP.NET and create my own custom advanced filter, and integrate DataTables.net component for presentation of the table. The solution presented here is what I came up with.
3 Advanced Filter Form
The custom advanced filter is just another ASP.NET form, and I decided to save the filter state into ASP.NET Session object, so that info is available to the DataTables component during each AJAX call. That way “filter state” is shared between different actions/forms in the application. Here is the relevant code.
public class HomeController : Controller
{
public const string EMPLOYEES_ADVANCED_FILTER_STATE = "EMPLOYEES_ADVANCED_FILTER_STATE";
public IActionResult EmployeesAdvancedFilter(EmployeesAdvancedFilterVM model)
{
try
{
ISession ? CurrentSession=this.HttpContext?.Session;
if (model.IsSubmit)
{
if (CurrentSession != null && model != null)
{
model.FirstName=model.FirstName?.Trim();
model.LastName = model.LastName?.Trim();
model.City = model.City?.Trim();
model.Country = model.Country?.Trim();
string jsonUserEmployeesAdvancedFilterState = JsonSerializer.Serialize(model);
CurrentSession.SetString(EMPLOYEES_ADVANCED_FILTER_STATE, jsonUserEmployeesAdvancedFilterState);
}
return RedirectToAction("Employees", "Home");
}
else if (model.IsReset)
{
CurrentSession?.Remove(EMPLOYEES_ADVANCED_FILTER_STATE);
}
{
string? jsonUserEmployeesAdvancedFilterState = CurrentSession?.GetString(EMPLOYEES_ADVANCED_FILTER_STATE);
if (!string.IsNullOrEmpty(jsonUserEmployeesAdvancedFilterState))
{
model = JsonSerializer.Deserialize<EmployeesAdvancedFilterVM>(jsonUserEmployeesAdvancedFilterState)
?? new EmployeesAdvancedFilterVM();
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return View(model);
}
namespace Example09.Models.Home
{
public class EmployeesAdvancedFilterVM
{
public string? FirstName { get; set; } = null;
public string? LastName { get; set; } = null;
public string? City { get; set; } = null;
public string? Country { get; set; } = null;
public bool IsSubmit { get; set; } = false;
public bool IsReset { get; set; } = false;
}
}
<!--
@using Example09.Models.Home;
@model EmployeesAdvancedFilterVM
@{
<div class="text-center">
<h3 class="display-4">Employees Advanced Filter</h3>
</div>
<fieldset class="border rounded-3 p-3" style="width:600px">
<legend class="float-none w-auto px-3">Choose filter parameters</legend>
<p class="bg-light m-1 border p-1">
* = whildchar, zero or more characters <br/>
? = whildchar, one character
</p>
<form id="form1" method="post">
<div class="form-group">
<label asp-for=FirstName>Given Name</label>
<input class="form-control" asp-for="FirstName" />
</div>
<div class="form-group">
<label asp-for=LastName>Family Name</label>
<input class="form-control" asp-for="LastName" />
</div>
<div class="form-group">
<label asp-for=City>Town</label>
<input class="form-control" asp-for="City" />
</div>
<div class="form-group">
<label asp-for=Country></label>
<input class="form-control" asp-for="Country" />
</div>
<button type="submit" form="form1" class="btn btn-primary mt-3 ms-3 float-end" asp-area=""
asp-controller="Home" asp-action="EmployeesAdvancedFilter" asp-route-IsSubmit="true">
Submit
</button>
<a class="btn btn-primary mt-3 float-end" asp-area=""
asp-controller="Home" asp-action="EmployeesAdvancedFilter" asp-route-IsReset="true">
Reset
</a>
</form>
</fieldset>
}
4 Client-side DataTables.net component
Here I will just show what the ASP.NET view using DataTables component looks like.
<!--
@using Example09.Models.Home;
@model EmployeesVM
<partial name="_LoadingDatatablesJsAndCss" />
@{
<div class="text-center">
<h3 class="display-4">Employees table - Advanced Filter</h3>
</div>
<h4 class="bg-info m-4 border p-3">
@{
string text1 = "Filter= " + Model?.AdvancedFilterState;
@text1
}
</h4>
<!-- Here is our table HTML element defined. JavaScript library Datatables
will do all the magic to turn it into interactive component -->
<table id="EmployeesTable01" class="table table-striped table-bordered ">
</table>
}
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", InitializeDatatable);
function InitializeDatatable() {
$("#EmployeesTable01").dataTable(
{
processing: true,
paging: true,
info: true,
ordering: true,
searching: false,
autoWidth: true,
lengthMenu: [10, 15, 25, 50, 100],
pageLength: 10,
order: [[1, 'asc']],
serverSide: true,
ajax: {
"url": "@Url.Action("EmployeesDT", "Home")",
"type": "POST",
"datatype": "json"
},
columns: [
{
name: 'id',
data: 'id',
title: "Employee Id",
orderable: true,
searchable: false,
type: 'num',
visible: false
},
{
name: 'givenName',
data: "givenName",
title: "Given Name",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
name: 'familyName',
data: "familyName",
title: "Family Name",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
name: 'town',
data: "town",
title: "Town",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
name: 'country',
data: "country",
title: "Country",
orderable: true,
searchable: true,
type: 'string',
visible: true,
width: "150px",
className: 'text-center '
},
{
name: 'email',
data: "email",
title: "Email",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
name: 'phoneNo',
data: "phoneNo",
title: "Phone Number",
orderable: false,
searchable: true,
type: 'string',
visible: true
}
],
layout: {
top1Start: {
buttons:
[
{
text: 'Filter',
action: AdvancedFilter
}
]
}
}
}
);
function AdvancedFilter(e, dt, node, config) {
let EmployeesAdvancedFilter = "@Url.Action("EmployeesAdvancedFilter", "Home")";
window.location.replace(EmployeesAdvancedFilter);
};
}
</script>
More about JavaScript properties can be found in the manual at [1]. The application here is just a proof of concept for ASP.NET environment.
5 ASP.NET back-end processing
So, we are now at C#/.NET part, writing our ASP.NET code. Note that in action Employees() and in method FilterRowsPerSavedAdvancedFilterState() we retrieve the filter state from ASP.NET Session object. The main trick here is that during each AJAX call, we get the filter state from the ASP.NET Session object and apply filter content to the back-end processing of the table.
namespace Example09.Models.Home
{
public class EmployeesVM
{
public String? AdvancedFilterState { get; set; } = null;
}
}
namespace Example09.Controllers
{
public class HomeController : Controller
{
public const string EMPLOYEES_ADVANCED_FILTER_STATE = "EMPLOYEES_ADVANCED_FILTER_STATE";
public IActionResult Employees(EmployeesVM model)
{
try
{
ISession? CurrentSession = this.HttpContext?.Session;
{
string? jsonUserEmployeesAdvancedFilterState = CurrentSession?.GetString(EMPLOYEES_ADVANCED_FILTER_STATE);
if (!string.IsNullOrEmpty(jsonUserEmployeesAdvancedFilterState))
{
EmployeesAdvancedFilterVM? AdvancedFilterState =
JsonSerializer.Deserialize<EmployeesAdvancedFilterVM>(jsonUserEmployeesAdvancedFilterState);
if (AdvancedFilterState != null)
{
string filterState =
"Given Name: " + AdvancedFilterState.FirstName +
"; Family Name: " + AdvancedFilterState.LastName +
"; Town: " + AdvancedFilterState.City+
"; Country: " + AdvancedFilterState.Country ;
model.AdvancedFilterState = filterState;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return View(model);
}
public IActionResult EmployeesDT(DataTables.AspNet.Core.IDataTablesRequest request)
{
try
{
IQueryable<Employee> employees = MockDatabase.MockDatabase.Instance.EmployeesTable.AsQueryable();
int totalRecordsCount = employees.Count();
employees = FilterRowsPerSavedAdvancedFilterState(employees);
var iQueryableOfAnonymous = employees.Select(p => new
{
id = p.Id,
givenName = p.FirstName,
familyName = p.LastName,
town = p.City,
country = p.Country,
email = p.Email,
phoneNo = p.Phone,
});
int filteredRecordsCount = iQueryableOfAnonymous.Count();
iQueryableOfAnonymous = SortRowsPerRequestParamters(iQueryableOfAnonymous, request);
iQueryableOfAnonymous = iQueryableOfAnonymous.Skip(request.Start).Take(request.Length);
var dataPage = iQueryableOfAnonymous.ToList();
var response = DataTablesResponse.Create(request, totalRecordsCount, filteredRecordsCount, dataPage);
return new DataTablesJsonResult(response, false);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
var response = DataTablesResponse.Create(request, "Error processing AJAX call on server side");
return new DataTablesJsonResult(response, false);
}
}
private IQueryable<Example09.MockDatabase.Employee> FilterRowsPerSavedAdvancedFilterState(
IQueryable<Example09.MockDatabase.Employee> iQueryableOfEmployee)
{
try
{
ISession? CurrentSession = this.HttpContext?.Session;
{
string? jsonUserEmployeesAdvancedFilterState = CurrentSession?.GetString(EMPLOYEES_ADVANCED_FILTER_STATE);
if (!string.IsNullOrEmpty(jsonUserEmployeesAdvancedFilterState))
{
EmployeesAdvancedFilterVM? advancedFilter =
JsonSerializer.Deserialize<EmployeesAdvancedFilterVM>(jsonUserEmployeesAdvancedFilterState);
if (advancedFilter != null)
{
advancedFilter.FirstName = advancedFilter.FirstName?.Trim();
if (!string.IsNullOrEmpty(advancedFilter.FirstName))
{
if (advancedFilter.FirstName.Contains('*') || advancedFilter.FirstName.Contains('?'))
{
string pattern = advancedFilter.FirstName.Replace("*", ".*").Replace("?", ".{1}");
iQueryableOfEmployee = iQueryableOfEmployee.Where(
vk => vk.FirstName != null && Regex.IsMatch(vk.FirstName, pattern));
}
else
{
iQueryableOfEmployee = iQueryableOfEmployee.Where(
vk => vk.FirstName != null && vk.FirstName.Equals(advancedFilter.FirstName));
}
}
advancedFilter.LastName = advancedFilter.LastName?.Trim();
if (!string.IsNullOrEmpty(advancedFilter.LastName))
{
if (advancedFilter.LastName.Contains('*') || advancedFilter.LastName.Contains('?'))
{
string pattern = advancedFilter.LastName.Replace("*", ".*").Replace("?", ".{1}");
iQueryableOfEmployee = iQueryableOfEmployee.Where(
vk => vk.LastName != null && Regex.IsMatch(vk.LastName, pattern));
}
else
{
iQueryableOfEmployee = iQueryableOfEmployee.Where(
vk => vk.LastName != null && vk.LastName.Equals(advancedFilter.LastName));
}
}
advancedFilter.City = advancedFilter.City?.Trim();
if (!string.IsNullOrEmpty(advancedFilter.City))
{
if (advancedFilter.City.Contains('*') || advancedFilter.City.Contains('?'))
{
string pattern = advancedFilter.City.Replace("*", ".*").Replace("?", ".{1}");
iQueryableOfEmployee = iQueryableOfEmployee.Where(
vk => vk.City != null && Regex.IsMatch(vk.City, pattern));
}
else
{
iQueryableOfEmployee = iQueryableOfEmployee.Where(
vk => vk.City != null && vk.City.Equals(advancedFilter.City));
}
}
advancedFilter.Country = advancedFilter.Country?.Trim();
if (!string.IsNullOrEmpty(advancedFilter.Country))
{
if (advancedFilter.Country.Contains('*') || advancedFilter.Country.Contains('?'))
{
string pattern = advancedFilter.Country.Replace("*", ".*").Replace("?", ".{1}");
iQueryableOfEmployee = iQueryableOfEmployee.Where(
vk => vk.Country != null && Regex.IsMatch(vk.Country, pattern));
}
else
{
iQueryableOfEmployee = iQueryableOfEmployee.Where(
vk => vk.Country != null && vk.Country.Equals(advancedFilter.Country));
}
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return iQueryableOfEmployee;
}
6 Conclusion
The full example code project can be downloaded.
7 References
[1] https://datatables.net/
[21] ASP.NET8 using DataTables.net – Part1 – Foundation
https://www.codeproject.com/Articles/5385033/ASP-NET-8-Using-DataTables-net-Part1-Foundation
[22] ASP.NET8 using DataTables.net – Part2 – Action buttons
https://www.codeproject.com/Articles/5385098/ASP-NET8-using-DataTables-net-Part2-Action-buttons
[23] ASP.NET8 using DataTables.net – Part3 – State saving
https://www.codeproject.com/Articles/5385308/ASP-NET8-using-DataTables-net-Part3-State-saving
[24] ASP.NET8 using DataTables.net – Part4 – Multilingual
https://www.codeproject.com/Articles/5385407/ASP-NET8-using-DataTables-net-Part4-Multilingual
[25] ASP.NET8 using DataTables.net – Part5 – Passing additional parameters in AJAX
https://www.codeproject.com/Articles/5385575/ASP-NET8-using-DataTables-net-Part5-Passing-additi
[26] ASP.NET8 using DataTables.net – Part6 – Returning additional parameters in AJAX
https://www.codeproject.com/Articles/5385692/ASP-NET8-using-DataTables-net-Part6-Returning-addi
[27] ASP.NET8 using DataTables.net – Part7 – Buttons regular
https://www.codeproject.com/Articles/5385828/ASP-NET8-using-DataTables-net-Part7-Buttons-regula
[28] ASP.NET8 using DataTables.net – Part8 – Select rows
https://www.codeproject.com/Articles/5386103/ASP-NET8-using-DataTables-net-Part8-Select-rows
[29] ASP.NET8 using DataTables.net – Part9 – Advanced Filters
https://www.codeproject.com/Articles/5386263/ASP-NET8-using-DataTables-net-Part9-Advanced-Filte