The purpose of this document is to demonstrate two different ways to use the DataTables.net JavaScript library in Blazor Web Assembly.
Blazor Web Assembly – Two Methods For Using DataTables.net JavaScript Library
Blazor Web Assembly is a great way to be able to write your client-side code in C#. However, JavaScript has been around for a very long time, and sometimes you will want to take advantage of these JavaScript libraries in your Blazor Web Assembly program. DataTables.net is a popular library used on many sites, and in this article, we will discuss two different methods of using this library.
Just show me the code: https://github.com/woodman231/BlazorWebAssemblyWithDataTablesDemo
Step 1 – Create .NET Core Hosted Blazor Web Assembly Project
First, open Visual Studio and Create a new project.
Next, select the Blazor Web Assembly project and then click on Next
Be sure to check the box “ASP.NET Core hosted” and then click on Create:
- It creates 3 projects:
- The Server has weatherforecasts route that randomly generates a weather forecast
- The Client has an example communication between the client and server to fetch and render that forecast
- It has Bootstrap 5, but does not have Jquery or Bootstrap JavaScript
- – Client
– Server
– Shared
Step 2 – Load the DataTables.net JavaScript library
DataTables.net is a Jquery extension. We will not only need the DataTables.net library but we will also need Jquery. In order to load Jquery and DataTables.net we will need to add the following tags in to the Client\wwwroot\index.html page. Add them in the head tag after the Client.styles.css reference.
<link href="https://cdn.datatables.net/1.12.1/css/dataTables.bootstrap5.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.6.1.min.js" type="text/javascript"></script>
<script src="https://cdn.datatables.net/1.12.1/js/jquery.dataTables.min.js" type="text/javascript"></script>
<script src="https://cdn.datatables.net/1.12.1/js/dataTables.bootstrap5.min.js" type="text/javascript"></script>
If you don’t want to download them via CDN you can always download these files to your projects wwwroot folder and update the href’s and src’s accordingly. But for this demonstration we will stick with the CDN references.
My index.html file looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>BlazorWebAssemblyWithDataTablesDemo</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="BlazorWebAssemblyWithDataTablesDemo.Client.styles.css" rel="stylesheet" />
<link href="https://cdn.datatables.net/1.12.1/css/dataTables.bootstrap5.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.6.1.min.js" type="text/javascript"></script>
<script src="https://cdn.datatables.net/1.12.1/js/jquery.dataTables.min.js" type="text/javascript"></script>
<script src="https://cdn.datatables.net/1.12.1/js/dataTables.bootstrap5.min.js" type="text/javascript"></script>
</head>
<body>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>
Method 1 – Blazor Rendered – DataTables Initialized
In this method we are going to allow Blazor to render the table, and then after the table is rendered, we will initialize DataTables using their zero-configuration setup. To be honest we are going to do a little bit of configuration to handle the date column, but other than that this method is zero configuration.
In the Client project and the Pages folder add a new Razor component called: “DataTables1.razor”.
Give it the following code:
@page "/datatables1"
@implements IDisposable
@using BlazorWebAssemblyWithDataTablesDemo.Shared
@inject HttpClient Http
@inject IJSRuntime JS
<PageTitle>Weather forecast - DataTables - Rendered / 0 Config</PageTitle>
<h1>Weather forecast - DataTables - Rendered / 0 Config</h1>
<p>This component demonstrates fetching data from the server. After which it will initialize a data table. Any time the data is refreshed, the datatable is destroyed and rebuilt.</p>
<button class="btn btn-primary mb-2" type="button" @onclick="RefreshForecastsAsync">Refresh</button>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table" id="dt1">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
await RefreshForecastsAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if(forecasts is not null)
{
await JS.InvokeVoidAsync("DataTables1.destroyDataTable");
await JS.InvokeVoidAsync("DataTables1.buildDataTable");
}
}
private async Task RefreshForecastsAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}
async void IDisposable.Dispose()
{
await JS.InvokeVoidAsync("DataTables1.destroyDataTable");
}
}
The difficult part about using DataTables in this method is allowing DataTables to do its thing, but also remove the DataTable before refreshing the data, and then re-adding the DataTable after the new data is available. Failing to do so will make it so the search and sort functions won’t work properly. Failing to destroy the DataTable when the user leaves the page will also leave some HTML Elements on the screen that are not applicable to the other pages that a user might browse to in your application.
You will notice that we have a couple of calls to JS.InvokeVoidAsync. We will build those JavaScript functions soon, but let’s discuss a little bit more about the code on this page.
We are implementing the IDisposable interface. To meet the requirements of that interface we added an async void IDisposable.Dispose() method which is going to call a JavaScript function to destroy the DataTable. This will be important to do while the user browses to other pages in our application.
When this component is initialized, it runs the RefreshForecastsAsync method which sets the forecasts property with results from an Http call to the weatherforecast endpoint. After the component is rendered, it will do whatever it normally does with an after render, and if there are some forecasts, then we will make a request to destroy, and then rebuild the DataTable.
When the refresh button is clicked it will set the forecasts property again, and as an after effect will run the OnAfterRenderAsync again. If there are forecasts, then it will destroy and rebuild the DataTable.
Now to write the JavaScript functions that we are telling these C# methods to Invoke.
In the Client\wwwroot\ folder create a js folder. To the js folder create a new file called DataTables1.js.
Give it the following code:
window.DataTables1 = {
dataTable: null,
buildDataTable: function () {
this.dataTable = $("#dt1").DataTable({
"columnDefs": [{
"targets": 0,
"render": function (data, type, row, meta) {
var dateValue = new Date(data);
if (type === 'display') {
return dateValue.toLocaleDateString();
}
else {
return dateValue.valueOf();
}
}
}],
});
},
destroyDataTable: function () {
if (this.dataTable) {
this.dataTable.destroy();
}
}
}
The buildDataTable property is a function that sets the dataTable property of the object using the DataTable jQuery extension. It is nearly zero configuration; however, in order for the date’s to truly sort and display correctly, we add a columnDefs option to target the first field (which is the date) and return a LocalDateString if the column is to be displayed, otherwise return the number of milliseconds from the Unix Epoch (January 1st, 1970) when being sorted.
The destroyDataTable property is a function that checks if the dataTable property has been set. If it has been set then it calls the destroy() function for its dataTable.
Load this JavaScript file in the wwwroot\index.html file by adding this tag after the framework/blazor.webassembly.js file:
<script src="js/DataTables1.js"></script>
<div class="nav-item px-3">
<NavLink class="nav-link" href="datatables1">
<span class="oi oi-list-rich" aria-hidden="true"></span> DataTable1
</NavLink>
</div>
Method 2 – DataTables Rendered with Ajax Input
One of the more powerful uses of DataTables is the ability to pull in data via an Ajax request and render the data from the Ajax request to a DataTable. Therefore, in this method we are shifting more responsibility to JavaScript and less from C#.
To the Pages folder add a new Razor component called DataTables2.razor.
Give it the following code:
@page "/datatables2"
@implements IDisposable
@using BlazorWebAssemblyWithDataTablesDemo.Shared
@inject HttpClient Http
@inject IJSRuntime JS
<PageTitle>Weather forecast - DataTables - Ajax</PageTitle>
<h1>Weather forecast - DataTables - Ajax</h1>
<p>This component demonstrates fetching data from the server. This will use DataTables Ajax functionality instead of C# HttpClient. The HttpClient is still injected as a convenient way to get the BaseAddress.</p>
<button class="btn btn-primary mb-2" type="button" @onclick="RefreshForecastsAsync">Refresh</button>
<table class="table" id="dt2">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
</table>
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
if (Http.BaseAddress is not null)
{
string forecastsUrl = Http.BaseAddress.ToString() + "weatherforecast";
await JS.InvokeVoidAsync("DataTables2.buildDataTable", forecastsUrl);
}
}
}
private async Task RefreshForecastsAsync()
{
await JS.InvokeVoidAsync("DataTables2.refreshDataTable");
}
async void IDisposable.Dispose()
{
await JS.InvokeVoidAsync("DataTables2.destroyDataTable");
}
}
This time we are just rendering the table straight away. The refresh method is now calling a JavaScript method instead of pulling the data and requesting an additional render.
When this component is first rendered it supplies the weatherforecast url (Ajax Url) to the JavaScript method of buildDataTable.
There is no need to destroy and rebuild the datatable on refresh this time because DataTables takes care of all that during the Ajax reload request.
In wwwroot\js add a new file called DataTables2.js.
Give it the following code:
window.DataTables2 = {
dataTable: null,
buildDataTable: function (ajaxUrl) {
this.dataTable = $("#dt2").DataTable({
"ajax": {
"url": ajaxUrl,
"dataSrc": "",
},
"columns": [
{
"data": "date",
"render": function (data, type, row, meta) {
var dateValue = new Date(data);
if (type === 'display') {
return dateValue.toLocaleDateString();
}
else {
return dateValue.valueOf();
}
}
},
{ "data": "temperatureC" },
{ "data": "temperatureF" },
{ "data": "summary" },
]
});
},
destroyDataTable: function () {
if (this.dataTable) {
this.dataTable.destroy();
}
},
refreshDataTable: function () {
if (this.dataTable) {
this.dataTable.ajax.reload();
}
}
}
<script src="js/DataTables2.js"></script>
<div class="nav-item px-3">
<NavLink class="nav-link" href="datatables2">
<span class="oi oi-list-rich" aria-hidden="true"></span> DataTable2
</NavLink>
</div>
Experiment with the refresh button and search functionality and sort functionality. Set some break points in your Visual Studio to test out the OnAfterRenderAsync, RefreshForecastsAsync and Dispose methods in DataTables2.razor.
Conclusion
Blazor Web Assembly certainly does put you in a spot to allow you to use your C# skills to write client-side code, but luckily you are not locked into it and can still use JavaScript interop capabilities to leverage powerful libraries that have existed before to provide the best user experience possible. Remember that Blazor is a Single Page Application (SPA) framework like React and Angular so you will still need to be aware of destroying these JavaScript objects before their DOM is removed to ensure a graceful exit of these components and ensure the best user experience.
GitHub Repo: https://github.com/woodman231/BlazorWebAssemblyWithDataTablesDemo
About Intertech
Intertech is a Software Development Consulting Firm that provides single and multiple turnkey software development teams, available on your schedule and configured to achieve success as defined by your requirements independently or in co-development with your team. Blackslate Software teams combine proven full-stack, DevOps, Agile-experienced lead consultants with Delivery Management, User Experience, Software Development, and QA experts in Business Process Automation (BPA), Microservices, Client- and Server-Side Web Frameworks of multiple technologies, Custom Portal and Dashboard development, Cloud Integration and Migration (Azure and AWS), and so much more. Each Blackslate Software employee leads with the soft skills necessary to explain complex concepts to stakeholders and team members alike and makes your business more efficient, your data more valuable, and your team better. In addition, Blackslate Software is a trusted partner of more than 4000 satisfied customers and has a 99.70% “would recommend” rating.