ASP.NET8 using DataTables.net – Part1 – Foundation
A practical guide to using jQuery DataTables.net component in Asp.Net 8 MVC application.
Abstract: A practical guide to building an Asp.Net 8 MVC application that uses jQuery component DataTables.net. The article focuses on integrating different technologies (ASP.Net8, MVC, C#, Bootstrap 5, jQuery DataTables.net) into a professional-level application.
1 ASP.NET8 using jQuery DataTables.net
I was searching for a freeware table component that would fit into my developing environment for my new project, which is ASP.NET8, C#, MVC, Bootstrap 5, EF. In a number of places, I saw mention of the jQuery DataTables.net component [1]. After shortly looking into different articles, I decided to create several prototype (proof-of-concept) applications to evaluate it for usage in my professional projects. These articles are the result of that evaluation.
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
Let us present the result of this article prototyping in ASP.NET8, C#, MVC, Bootstrap 5 environment. Here is what you get:
You get a nice-looking table that feeds its data from background AJAX calls. The table itself, which you see in the picture is a jQuery component, and you write a back-end processing in C#. Any interaction with any of the green areas in this picture triggers a new AJAX call to the ASP.NET server with the new parameters which returns a new set of data.
The benefit is that you do not need to write all the HTML/JavaScript to create the table, that is provided to you by the DataTables.net component. Of course, you need to learn the component API, and you are a bit limited by the UI options that the component is providing you.
2.1 Positive impressions, but with reserve
My impressions are generally positive and I am going to use DataTables.net component in my projects. The logic is, that I need such a component to preset my tables, and if not using this one, I would need to develop my own version with similar functionality to present my tables.
However, not everything is ideal. First of all, I already hit a bug in DataTables.net, which I reported and I depend on the Open Source community to resolve it. Next, not all is freeware, the Editor plug-in is commercial and is like $119 for a single license/year.
The problem is for C# usage that DataTables.AspNet.Core library ([2],[3]) is no longer maintained since 2022. That is Open Source library I am using on ASP.NET/C# side for back-end processing. There are some bugs, and you are on your own to work around them.
3 What is DataTables.net
DataTables.net is jQuery UI library and is well described on site [1], so I will not repeat all that here. Just to mention that it works in 2 modes:
- Client-side processing
- Server-side processing
In mode 1) all table data (let's say 1000 rows) is loaded by the component into the browser and all filtering and sorting are done on the client side in the browser using JavaScript. From a professional usage point of view, that is a kind of naïve mode of work.
In mode 2), the component makes AJAX calls to load only data that is going to be present (let's say 15 rows) and after each user interaction with the component, a new AJAX call is made to get the new data. In this article, we are interested only in this mode of work, which is the only one suitable for professional usage.
3.1 How to get DataTables.net
Site [1] describes several ways to get the component, including CDN and NuGet packages. Not all worked well enough for me, so I found the best for me was to download it directly from the site and deploy it directly in the project:
<!-- _LoadingDatatablesJsAndCss.cshtml -->
<link rel="stylesheet" href="~/lib/datatables/datatables.min.css" asp-append-version="true" />
<script src="~/lib/datatables/datatables.min.js" defer asp-append-version="true"></script>
3.2 Literature
When dealing with the Open Source, it is necessary to collect all the literature available. I made a list of references, and here is an outline:
- DataTables.net component is at [1]. There is a nice manual describing API and all the options there
- DataTables.AspNet.Core library is described at [2], [3]. That is C#/.NET library for ASP.NET back-end processing. There are some examples of how to use it on the GitHub site.
- Various articles and examples are from [4]-[11]. If you find this tutorial too difficult, you might find some other article there easier to start with.
- System.Linq.Dynamic.Core library is described at [12]-[14]. It is a useful library for C# back-end processing
4 Integration tutorial
The focus of this tutorial is the integration of different components in ASP.NET8, C#, MVC, Bootstrap 5, JavaScript, jQuery environment. I will not be talking much about individual components, they have been described elsewhere. I do not plan to repeat it all here.
4.1 Tutorial for Professionals
I will not pretend that this is an easy topic. This is a tutorial for experienced professionals. I will just point into the direction I am going and additional literature, and that is it. It is a waste of time and focus to detail all here. I assume knowledge of AJAX, Linq, JavaScript, MVC, ASP.NET etc.
5 Mock database
What I want to present in my prototype in the HTML table in Web UI is my entity Employee. To make the example independent from any database setup, I created a Mock database in the memory, basically a JSON with 1000 rows. Here is the code:
//Employee.cs =========================================================
namespace Example01.MockDatabase
{
public class Employee
{
public int Id { get; set; } = 0;
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? City { get; set; }
public string? Country { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
}
}
//MockDatabase.cs ======================================================
namespace Example01.MockDatabase
{
public class MockDatabase
{
//table EmployeesTable as an instance
private List<Employee>? _employeeTable;
public List<Employee> EmployeesTable
{
get
{
//going for singleton pattern
if( _employeeTable == null )
{
Employee[]? employeeArray = JsonSerializer.Deserialize<Employee[]>(MockEmployeeData.Data);
if( employeeArray != null )
{
_employeeTable = new List<Employee>();
_employeeTable.AddRange( employeeArray );
}
}
if (_employeeTable == null) { _employeeTable = new List<Employee>(); };
return _employeeTable;
}
}
//database MockDatabase as an instance
private static MockDatabase? _instance;
public static MockDatabase Instance {
get
{
//going for singleton pattern
if (_instance==null)
{
_instance = new MockDatabase();
}
return _instance;
}
}
}
}
//MockEmployeeData.cs =========================================
namespace Example01.MockDatabase
{
public static class MockEmployeeData
{
public static string Data=
@"
[{""Id"":1,""FirstName"":""Saunder"",""LastName"":""Hedger"",""City"":""Poitiers"",""Country"":""France"",""Email"":""shedger0@naver.com"",""Phone"":""170-116-2494""},
{""Id"":2,""FirstName"":""Cornell"",""LastName"":""De la Zenne"",""City"":""San Antonio de los Cobres"",""Country"":""Argentina"",""Email"":""cdelazenne1@narod.ru"",""Phone"":""594-372-0412""},
{""Id"":3,""FirstName"":""Caryn"",""LastName"":""Scarsbrooke"",""City"":""Pelasgía"",""Country"":""Greece"",""Email"":""cscarsbrooke2@sciencedaily.com"",""Phone"":""184-707-0966""},
{""Id"":4,""FirstName"":""Graham"",""LastName"":""Sommerfeld"",""City"":""Nijmegen"",""Country"":""Netherlands"",""Email"":""gsommerfeld3@google.ru"",""Phone"":""927-828-0615""},
{""Id"":5,""FirstName"":""Rosy"",""LastName"":""Sparway"",""City"":""Wiesbaden"",""Country"":""Germany"",""Email"":""rsparway4@goo.gl"",""Phone"":""877-176-1489""},
{""Id"":6,""FirstName"":""Lesli"",""LastName"":""Heaton"",""City"":""Vilhena"",""Country"":""Brazil"",""Email"":""lheaton5@hugedomains.com"",""Phone"":""540-215-7615""},
{""Id"":7,""FirstName"":""Kevon"",""LastName"":""Rumble"",""City"":""Yangxi"",""Country"":""China"",""Email"":""krumble6@usatoday.com"",""Phone"":""805-371-5794""},
{""Id"":8,""FirstName"":""Keefe"",""LastName"":""Ainsley"",""City"":""Cabriz"",""Country"":""Portugal"",""Email"":""kainsley7@jugem.jp"",""Phone"":""140-378-1955""},
{""Id"":9,""FirstName"":""Ninon"",""LastName"":""Baptist"",""City"":""Susapaya"",""Country"":""Peru"",""Email"":""nbaptist8@si.edu"",""Phone"":""111-388-0159""},
{""Id"":10,""FirstName"":""Moira"",""LastName"":""Edwinson"",""City"":""Wangchang"",""Country"":""China"",""Email"":""medwinson9@sphinn.com"",""Phone"":""844-869-9886""},
{""Id"":11,""FirstName"":""Isidoro"",""LastName"":""Meach"",""City"":""Qinglin"",""Country"":""China"",""Email"":""imeacha@huffingtonpost.com"",""Phone"":""951-769-9045""},
{""Id"":12,""FirstName"":""Norry"",""LastName"":""Leggan"",""City"":""Malummaduri"",""Country"":""Nigeria"",""Email"":""nlegganb@altervista.org"",""Phone"":""601-917-3867""},
.....etc...1000 records
6 Client-side DataTables.net component
Here I will just show what the ASP.NET view using DataTables component looks like. I will not here go over all the configuration properties, they are all nicely documented in the manual at [1]. I assume you will learn from [1] a bit about the component and configuration. Here I am just showing how to integrate it with ASP.NET. I added some comments though. I assume you can read JavaScript/jQuery code.
Programming styles differ, and I used the style I consider most suitable for my professional project. I prefer things explicitly outlined, not everyone likes that style.
Notice the configuration part for AJAX. This component reacts to user input by making AJAX calls and getting proper data.
<!-- Employees.cshtml -->
<partial name="_LoadingDatatablesJsAndCss" />
@{
<div class="text-center">
<h3 class="display-4">Employees table</h3>
</div>
<!-- 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">
// Datatables script initialization =========================================
// we used defer attribute on jQuery so it might not be available at this point
// so we go for vanilla JS event
document.addEventListener("DOMContentLoaded", InitializeDatatable);
function InitializeDatatable() {
$("#EmployeesTable01").dataTable(
//providing initialization parameters as JavaScript object
{
//processing-Feature control the processing indicator.
processing: true,
//paging-Enable or disable table pagination.
paging: true,
//info-Feature control table information display field
info: true,
//ordering-Feature control ordering (sorting) abilities in DataTables.
ordering: true,
//searching-Feature control search (filtering) abilities
searching: true,
//search.return-Enable / disable DataTables' search on return.
search: {
return: true
},
//autoWidth-Feature control DataTables' smart column width handling.
autoWidth: true,
//lengthMenu-Change the options in the page length select list.
lengthMenu: [10, 15, 25, 50, 100],
//pageLength-Change the initial page length (number of rows per page)
pageLength: 15,
//order-Initial order (sort) to apply to the table.
order: [[1, 'asc']],
//serverSide-Feature control DataTables' server-side processing mode.
serverSide: true,
//Load data for the table's content from an Ajax source.
ajax: {
"url": "@Url.Action("EmployeesDT", "Home")",
"type": "POST",
"datatype": "json"
},
//Set column specific initialisation properties.
columns: [
//name-Set a descriptive name for a column
//data-Set the data source for the column from the rows data object / array
//title-Set the column title
//orderable-Enable or disable ordering on this column
//searchable-Enable or disable search on the data in this column
//type-Set the column type - used for filtering and sorting string processing
//visible-Enable or disable the display of this column.
//width-Column width assignment.
//render-Render (process) the data for use in the table.
//className-Class to assign to each cell in the column.
{ //0
name: 'id',
data: 'id',
title: "Employee Id",
orderable: true,
searchable: false,
type: 'num',
visible: false
},
{
//1
name: 'givenName',
data: "givenName",
title: "Given Name",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
//2
name: 'familyName',
data: "familyName",
title: "Family Name",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
//3
name: 'town',
data: "town",
title: "Town",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
//4
name: 'country',
data: "country",
title: "Country",
orderable: true,
searchable: true,
type: 'string',
visible: true,
width: "150px",
className: 'text-center '
},
{
//5
name: 'email',
data: "email",
title: "Email",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
//6
name: 'phoneNo',
data: "phoneNo",
title: "Phone Number",
orderable: false,
searchable: true,
type: 'string',
visible: true
}
]
} // end of initialization object
);
}
</script>
7 Sample AJAX call
To better understand what is going on, I used Chrome DevTools to capture data going out and back in a sample AJAX call.
Here is the request:
draw: 2
columns[0][data]: id
columns[0][name]: id
columns[0][searchable]: false
columns[0][orderable]: true
columns[0][search][value]:
columns[0][search][regex]: false
columns[1][data]: givenName
columns[1][name]: givenName
columns[1][searchable]: true
columns[1][orderable]: true
columns[1][search][value]:
columns[1][search][regex]: false
columns[2][data]: familyName
columns[2][name]: familyName
columns[2][searchable]: true
columns[2][orderable]: true
columns[2][search][value]:
columns[2][search][regex]: false
columns[3][data]: town
columns[3][name]: town
columns[3][searchable]: true
columns[3][orderable]: true
columns[3][search][value]:
columns[3][search][regex]: false
columns[4][data]: country
columns[4][name]: country
columns[4][searchable]: true
columns[4][orderable]: true
columns[4][search][value]:
columns[4][search][regex]: false
columns[5][data]: email
columns[5][name]: email
columns[5][searchable]: true
columns[5][orderable]: true
columns[5][search][value]:
columns[5][search][regex]: false
columns[6][data]: phoneNo
columns[6][name]: phoneNo
columns[6][searchable]: true
columns[6][orderable]: false
columns[6][search][value]:
columns[6][search][regex]: false
order[0][column]: 1
order[0][dir]: asc
order[0][name]: givenName
start: 0
length: 15
search[value]: mark
search[regex]: false
Here is the response:
{
"draw": 2,
"recordsTotal": 1000,
"recordsFiltered": 7,
"data": [
{
"id": 867,
"givenName": "Ambur",
"familyName": "Key",
"town": "San Pedro de Macorís",
"country": "Dominican Republic",
"email": "akeyo2@domainmarket.com",
"phoneNo": "196-987-6349"
},
{
"id": 224,
"givenName": "Artus",
"familyName": "Ledekker",
"town": "Renxian",
"country": "China",
"email": "aledekker67@domainmarket.com",
"phoneNo": "480-208-2782"
},
{
"id": 60,
"givenName": "Eolanda",
"familyName": "Briamo",
"town": "Francisco I Madero",
"country": "Mexico",
"email": "ebriamo1n@domainmarket.com",
"phoneNo": "467-917-3423"
},
{
"id": 15,
"givenName": "Ermanno",
"familyName": "Sirett",
"town": "København",
"country": "Denmark",
"email": "esirette@about.com",
"phoneNo": "249-103-3769"
},
{
"id": 822,
"givenName": "Jany",
"familyName": "Cleen",
"town": "Shādegān",
"country": "Iran",
"email": "jcleenmt@marketwatch.com",
"phoneNo": "605-915-2463"
},
{
"id": 416,
"givenName": "Sunshine",
"familyName": "Sandbrook",
"town": "Shigu",
"country": "China",
"email": "ssandbrookbj@marketwatch.com",
"phoneNo": "479-324-0262"
},
{
"id": 406,
"givenName": "Teresina",
"familyName": "Sreenan",
"town": "Staryy Saltiv",
"country": "Ukraine",
"email": "tsreenanb9@domainmarket.com",
"phoneNo": "278-996-2943"
}
]
}
Here is the table generated:
I of course extracted only relevant data parts. Every better Web programmer should be able to understand what is going on from the above data.
8 DataTables.AspNet.Core library
If you are ASP.NET back-end programmer, the question from the above sample AJAX call is how to parse all that data input on the server side.
The answer is, I am using DataTables.AspNet.Core library [2], [3]. That library is both a solution and a problem. It provides useful utility freely available but is no longer maintained since 2022. My thinking is, that if I do not use that library, I will need to write my own version of the same, probably coming to very similar design solutions. So, I decided to use it and patch it when needed.
How do you learn that library? There are no tutorials available. So, I just downloaded the source code from GitHub and learned it from the source code. There are also some examples available as demos at the same site.
From now on, I will just be using that library in my code in this article.
9 System.Linq.Dynamic.Core library
There is another library that is useful for back-end processing in this application, that is System.Linq.Dynamic.Core library [12]-[14]. There is a nice tutorial available at [14]. From now on, I will just be using that library in my code in this article.
10 ASP.NET back-end processing
So, we are now at C#/.NET part, writing our ASP.NET code. Here is the solution I came up with. I will not pretend it is easy, it assumes a good understanding of libraries DataTables.AspNet.Core and System.Linq.Dynamic.Core. I exploit those libraries and LINQ to solve the problem.
I was going for a generic solution and reusable code. Otherwise, for each table, I would need to again write filter and sort methods specifically tailored to each new entity.
Here is the code:
//HomeController.cs ======================================
//this is target of AJAX call and provides data for
//the table, based on selected input parameters
public IActionResult EmployeesDT(DataTables.AspNet.Core.IDataTablesRequest request)
{
// There is dependency in this method on names of fields
// and implied mapping. I see it almost impossible to avoid.
// At least, in this method, we avoided dependency on the order
// of table fields, in case order needs to be changed
//Here are our mapped table columns:
//Column0 id -> Employee.Id
//Column1 givenName -> Employee.FirstName
//Column2 familyName -> Employee.LastName
//Column3 town -> Employee.City
//Column4 country -> Employee.Country
//Column5 email -> Employee.Email
//Column6 phoneNo -> Employee.Phone
try
{
IQueryable<Employee> employees = MockDatabase.MockDatabase.Instance.EmployeesTable.AsQueryable();
//here we get the count that needs to be presented by the UI
int totalRecordsCount = employees.Count();
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,
});
iQueryableOfAnonymous = FilterRowsPerRequestParameters(iQueryableOfAnonymous, request);
//here we get the count that needs to be presented by the UI
int filteredRecordsCount = iQueryableOfAnonymous.Count();
iQueryableOfAnonymous = SortRowsPerRequestParamters(iQueryableOfAnonymous, request);
iQueryableOfAnonymous = iQueryableOfAnonymous.Skip(request.Start).Take(request.Length);
//here we materialize the query
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<T> SortRowsPerRequestParamters<T>(
IQueryable<T> iQueryableOfAnonymous, DataTables.AspNet.Core.IDataTablesRequest request)
{
/*
* So, in "IQueryable<T> iQueryableOfAnonymous" I have the source data that I need to
* sort. In "DataTables.AspNet.Core.IDataTablesRequest request" I have full specification
* of sorting required, including the specification of columns
* that need to be sorted by marked with "IsSortable" and "Sort.Order".
* I use Reflection to check if names of entity T properties match column names in
* DataTables.AspNet.Core.IDataTablesRequest request and then create sort Linq query using
* System.Linq.Dynamic.Core library, and return Linq query as a result.
* It might look complicated, but advantage is that this method is generic and can
* be applied many times on different entities T.
*/
if (request != null && request.Columns != null && request.Columns.Any())
{
//this will work if type T contains properties that have names column.Name
var sortingColumns = request.Columns.Where(p => p.IsSortable && p.Sort != null).OrderBy(p => p.Sort.Order).ToList();
Type objType = typeof(T);
var ListOfAllPropertiesInT = objType.GetProperties().Select(p => p.Name).ToList();
if (sortingColumns != null && sortingColumns.Count > 0)
{
//we plan to build dynamic Linq expression in this string
string dynamicLinqOrder = string.Empty;
bool isFirstString = true;
for (int i = 0; i < sortingColumns.Count; i++)
{
var column = sortingColumns[i];
//check if that property exists in T,
//otherwise we will create syntax error in dynamic linq
if (ListOfAllPropertiesInT.Contains(column.Name))
{
if (isFirstString)
{
isFirstString = false;
}
else
{
dynamicLinqOrder += ", ";
}
dynamicLinqOrder += column.Name;
if (column.Sort.Direction == SortDirection.Descending)
{
dynamicLinqOrder += " desc";
}
}
}
if (dynamicLinqOrder.Length > 0)
{
//using System.Linq.Dynamic.Core
iQueryableOfAnonymous = iQueryableOfAnonymous.OrderBy(dynamicLinqOrder);
}
};
}
return iQueryableOfAnonymous;
}
private IQueryable<T> FilterRowsPerRequestParameters<T>(
IQueryable<T> iQueryableOfAnonymous, DataTables.AspNet.Core.IDataTablesRequest request)
{
/*
* So, in "IQueryable<T> iQueryableOfAnonymous" I have the source data that I need to
* filter. In "DataTables.AspNet.Core.IDataTablesRequest request" I have full specification
* of request, including the search value "request.Search.Value" and specification of columns
* that need to be searched for marked by "IsSearchable".
* I use Reflection to check if names of entity T properties match column names in
* DataTables.AspNet.Core.IDataTablesRequest request and then create filter Linq query using
* System.Linq.Dynamic.Core library, and return Linq query as a result.
* It might look complicated, but advantage is that this method is generic and can
* be applied many times on different entities T.
*/
//this will work if type T contains properties that have names column.Name
if (request != null && request.Search != null && !System.String.IsNullOrEmpty(request.Search.Value))
{
string pattern = request.Search.Value?.Trim() ?? System.String.Empty;
var searchingColumns = request.Columns.Where(p => p.IsSearchable).ToList();
var config = new ParsingConfig { ResolveTypesBySimpleName = true };
Type objType = typeof(T);
var ListOfAllPropertiesInT = objType.GetProperties().Select(p => p.Name).ToList();
if (searchingColumns.Count > 0)
{
//we plan to build dynamic Linq expression in this string
string dynamicLinqSearch = string.Empty;
bool isFirstString = true;
for (int i = 0; i < searchingColumns.Count; i++)
{
var column = searchingColumns[i];
//check if that property exists in T,
//otherwise we will create syntax error in dynamic linq
if (ListOfAllPropertiesInT.Contains(column.Name))
{
if (isFirstString)
{
isFirstString = false;
}
else
{
dynamicLinqSearch += " or ";
}
dynamicLinqSearch += $"""{column.Name}.Contains("{pattern}")""";
}
}
if (dynamicLinqSearch.Length > 0)
{
//using System.Linq.Dynamic.Core
iQueryableOfAnonymous = iQueryableOfAnonymous.Where(config, dynamicLinqSearch);
}
}
}
return iQueryableOfAnonymous;
}
11 Conclusion
DataTables.net component looks nice and worked reasonably well in my prototype. Learning effort is not small, but if the component is used frequently in many projects, it pays off. It is good to have such a component in your toolset if you are an ASP.NET developer.
The problem with Open Source code is that you depend on the community to keep up-to-date components/libraries that you integrate into your code. Bugs happen, and that can be a problem. In this particular case, library DataTables.AspNet.Core is no longer maintained, so if interfaces of DataTables.net or ASP.NET change, you are on your own to fix the issue.
The full example code project can be downloaded at GitHub [99].
12 References
[2] https://www.nuget.org/packages/DataTables.AspNet.Core/
DataTables.AspNet.Core
[3] https://github.com/ALMMa/datatables.aspnet
[4] https://www.c-sharpcorner.com/article/using-datatables-grid-with-asp-net-mvc/
Using DataTables Grid With ASP.NET MVC (MVC5)
[5] https://www.c-sharpcorner.com/article/display-loading-or-processing-message-inside-datatable/
Display Loading or Processing Message Inside DataTable
[6] https://www.c-sharpcorner.com/article/create-datatable-in-jquery/
Create a datatable in JQuery (pure jQuery)
[7] https://www.c-sharpcorner.com/article/server-side-rendering-of-datatables-js-in-asp-net-core/
Server Side Rendering Of DataTables JS In ASP.NET Core (2024)
[8] https://www.c-sharpcorner.com/article/effortless-pagination-with-jquery-datatables-and-bootstrap/
Effortless Pagination with jQuery DataTables and Bootstrap (very basic)
[9] https://www.c-sharpcorner.com/article/asp-net-mvc-jquery-server-side-datatable-example/
ASP.NET MVC jQuery Server Side Datatable Example
[10] https://www.c-sharpcorner.com/article/pagination-in-mvc-with-jquery-datatable/
Pagination In MVC With Jquery DataTable
[11] https://codewithmukesh.com/blog/jquery-datatable-in-aspnet-core/#google_vignette
JQuery Datatable in ASP.NET Core – Server-Side Processing
[12] https://www.nuget.org/packages/System.Linq.Dynamic.Core
System.Linq.Dynamic.Core
[13] https://github.com/zzzprojects/System.Linq.Dynamic.Core
zzzprojects/ System.Linq.Dynamic.Core
[14] https://dynamic-linq.net/
A FREE & Open Source LINQ Dynamic Query Library