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

I will be calling my project “BlazorWebAssemblyWithDataTablesDemo”. You can name yours whatever you like.

Be sure to check the box “ASP.NET Core hosted” and then click on Create:

A couple of things to note about the project that we created:

    • It creates 3 projects:
      • – Client
        – Server
        – Shared
    • 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

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>
I found these tags by searching for “jquery cdn” and “datatables cdn”.

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>
Now that we have Jquery and DataTables JavaScript libraries on the page we are ready to use them.

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");
    }
}
If you review the “FetchData.razor” and this file, you will see a lot of similarities.

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();
        }        
    }
}
I chose to write it this way to make window.DataTables1 a defacto namespace. The dataTable property is initialized as null and is set during the buildDataTable process.

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>
Add a link to the datatables1 page on the Shared\NavMenu.razor page just after the fetchdata link:
 <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>
Debug and browse the application. Go to the new datatables1 page.
You should now see a DataTable with the randomized Weather forecast data. Experiment with the Refresh and Search functionality that is now there. Compare this to the “Fetch data” page as well. Feel free to put break points in your Visual Studio to see how the OnInitializedAsync, OnAfterRenderAsync, RefreshForecastsAsync, and Dispose methods work on this page.
This method is not bad. We are using the C# HttpClient to fetch data, render it on screen using Blazor / C# code and then transform that table in to a DataTable using JavaScript. This is very similar to what you may do in a server side rendered application such as a Razor Pages or MVC application.

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");
    }
}
Again, we are implementing the IDisposable interface and satisfying the requirements of the interface by adding an async void IDisposable.Dispose method which again will destroy the datatable with a JavaScript request.

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();
        }
    }
}
Add a reference to this JavaScript file in the index.html file by adding this just after the place that you placed the reference to the DataTables1.js file:
<script src="js/DataTables2.js"></script>
Add a link to the DataTables2 page to the Shared\NavMenu.razor page:
<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>
Run and debug your application. Then test out the DataTables2 link.

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.

Even by the sheer number of lines of code between DataTables1.razor, DataTables2.razor, DataTables1.js, and DataTables2.js we can see the shift in responsibility between JavaScript and C#. The trick to this method though is making JavaScript aware of the correct URL to use. In this example I used C# to tell JavaScript which URL to use. We could have also statically programmed in the URL to the JavaScript file as well, or perhaps used some window.location properties to determine the URL to use as well.

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. Intertech 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 Intertech 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, Intertech is a trusted partner of more than 4000 satisfied customers and has a 99.70% “would recommend” rating.