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.NET 8, 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, which 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 with the UI options that the component is providing to 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:
<!--
<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:
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; }
}
}
namespace Example01.MockDatabase
{
public class MockDatabase
{
private List<Employee>? _employeeTable;
public List<Employee> EmployeesTable
{
get
{
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;
}
}
private static MockDatabase? _instance;
public static MockDatabase Instance {
get
{
if (_instance==null)
{
_instance = new MockDatabase();
}
return _instance;
}
}
}
}
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.
<!--
<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">
document.addEventListener("DOMContentLoaded", InitializeDatatable);
function InitializeDatatable() {
$("#EmployeesTable01").dataTable(
{
processing: true,
paging: true,
info: true,
ordering: true,
searching: true,
search: {
return: true
},
autoWidth: true,
lengthMenu: [10, 15, 25, 50, 100],
pageLength: 15,
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
}
]
}
);
}
</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:
public IActionResult EmployeesDT(DataTables.AspNet.Core.IDataTablesRequest request)
{
try
{
IQueryable<Employee> employees = MockDatabase.MockDatabase.Instance.EmployeesTable.AsQueryable();
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);
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<T> SortRowsPerRequestParamters<T>(
IQueryable<T> iQueryableOfAnonymous, DataTables.AspNet.Core.IDataTablesRequest request)
{
if (request != null && request.Columns != null && request.Columns.Any())
{
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)
{
string dynamicLinqOrder = string.Empty;
bool isFirstString = true;
for (int i = 0; i < sortingColumns.Count; i++)
{
var column = sortingColumns[i];
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)
{
iQueryableOfAnonymous = iQueryableOfAnonymous.OrderBy(dynamicLinqOrder);
}
};
}
return iQueryableOfAnonymous;
}
private IQueryable<T> FilterRowsPerRequestParameters<T>(
IQueryable<T> iQueryableOfAnonymous, DataTables.AspNet.Core.IDataTablesRequest request)
{
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)
{
string dynamicLinqSearch = string.Empty;
bool isFirstString = true;
for (int i = 0; i < searchingColumns.Count; i++)
{
var column = searchingColumns[i];
if (ListOfAllPropertiesInT.Contains(column.Name))
{
if (isFirstString)
{
isFirstString = false;
}
else
{
dynamicLinqSearch += " or ";
}
dynamicLinqSearch += $"""{column.Name}.Contains("{pattern}")""";
}
}
if (dynamicLinqSearch.Length > 0)
{
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.
12 References
[1] https://datatables.net/
[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
[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