3
votes

I am new to Datatables with AJAX calls. I need to implement server-side processing for thousands of records. I was following a tutorial but I got confused somewhere with server side processing.

I am getting an error while trying to render the data from the data source. Let me post all the relevant code and hope that you help me identify where I am getting it wrong. Since I am new to Datatables and Ajax, I will appreciate more answers with examples, textual answers accompanied by code or link(s) to a tutorial demostrating server side processing in ASP.NET CORE 3.1.

I appreciate your time and help.

NB. I took the implementation and configurations from a tutorial and tried to customize it.

Below is the HTML table and its model:

@model IEnumerable<StudentApplications>
<table id="custom-datatable" class="mb-5 display table table-bordered" style="width:100%">
                <thead>
                    <tr>
                        <th>
                            @Html.DisplayNameFor(model => model.ApplicationId)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.Firstname)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.Surname)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.ApplicationFor)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.SubmissionDate)
                        </th>
                        <th></th>
                    </tr>
                </thead>
                <tfoot>
                    <tr>
                        <th>
                            @Html.DisplayNameFor(model => model.ApplicationId)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.Firstname)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.Surname)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.ApplicationFor)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.SubmissionDate)
                        </th>
                        <th></th>
                    </tr>
                </tfoot>
            </table>

Below is the javascript file initialising an HTML table to datatable and some configurations:

$(document).ready(function () {
    $('#custom-datatable').DataTable({

        // Design Assets
        stateSave: true,
        autoWidth: true,
        // ServerSide Setups
        processing: true,
        serverSide: true,
        // Paging Setups
        paging: true,
        // Searching Setups
        searching: { regex: true },
        // Ajax Filter
        ajax: {
            url: "/myapplications/data",
            type: "POST",
            contentType: "application/json",
            dataType: "json",
            data: function (d) {
                return JSON.stringify(d);
            }
        },
        // Columns Setups
        columns: [
            { data: "ApplicationId" },
            { data: "Firstname" },
            { data: "Surname" },
            { data: "ApplicationFor" },
            { data: "SubmissionDate" }

            //// For Student.Id
            //{ "data": "applicationId", "name": "ApplicationId" },
            //// For Student.Firstname
            //{ "data": "firstname", "name": "Firstname" },
            //// For Student.Surname
            //{ "data": "surname", "name": "Surname" },
            //// For Student.ApplicationFor
            //{ "data": "applicationFor", "name": "ApplicationFor" },
            //// For Student.SubmissionDate
            //{ "data": "submissionDate", "name": "SubmissionDate" }
        ],
        // Column Definitions
        columnDefs: [
            { targets: "no-sort", orderable: false },
            { targets: "no-search", searchable: false },
            {
                targets: "trim",
                render: function (data, type, full, meta) {
                    if (type === "display") {
                        data = strtrunc(data, 10);
                    }

                    return data;
                }
            },
            { targets: "date-type", type: "date-eu" },
            {
                targets: 10,
                data: null,
                orderable: false
            },
        ]
    });

});

Below is the method that gets the data:

// loading datatable
        [HttpPost]
        [Route("myapplications/data")]
        public async Task<IActionResult> Data([FromBody] DtParameters dtParameters)
        { 
            searchBy = dtParameters.Search?.Value;

            // if we have an empty search then just order the results by Id ascending
            var orderCriteria = "ApplicationId";
            var orderAscendingDirection = true;

            if (dtParameters.Order != null)
            {
                // in this example we just default sort on the 1st column
                orderCriteria = dtParameters.Columns[dtParameters.Order[0].Column].Data;
                orderAscendingDirection = dtParameters.Order[0].Dir.ToString().ToLower() == "asc";
            }

            var result = context.AspNetStudentApplications.AsQueryable();

            if (!string.IsNullOrEmpty(searchBy))
            {
                result = result.Where(r => r.ApplicationId != null && r.ApplicationId.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.Firstname != null && r.Firstname.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.Surname != null && r.Surname.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.ApplicationFor != null && r.ApplicationFor.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.SubmissionDate != null && r.SubmissionDate.ToUpper().Contains(searchBy.ToUpper()));
            }

            result = orderAscendingDirection ? result.OrderByDynamic(orderCriteria, DtOrderDir.Asc) : result.OrderByDynamic(orderCriteria, DtOrderDir.Desc);

            // now just get the count of items (without the skip and take) - eg how many could be returned with filtering
            var filteredResultsCount = await result.CountAsync();
            var totalResultsCount = await context.AspNetStudentApplications.CountAsync();

            return Json(new DtResult<StudentApplications>
            {
                Draw = dtParameters.Draw,
                RecordsTotal = totalResultsCount,
                RecordsFiltered = filteredResultsCount,
                Data = await result
                    .Skip(dtParameters.Start)
                    .Take(dtParameters.Length)
                    .ToListAsync()
            });
        }

Below is the DtParameters class and other classes related to Datatables:

/// <summary>
/// A full result, as understood by jQuery DataTables.
/// </summary>
/// <typeparam name="T">The data type of each row.</typeparam>
public class DtResult<T>
{
    /// <summary>
    /// The draw counter that this object is a response to - from the draw parameter sent as part of the data request.
    /// Note that it is strongly recommended for security reasons that you cast this parameter to an integer, rather than simply echoing back to the client what it sent in the draw parameter, in order to prevent Cross Site Scripting (XSS) attacks.
    /// </summary>
    [JsonProperty("draw")]
    public int Draw { get; set; }

    /// <summary>
    /// Total records, before filtering (i.e. the total number of records in the database)
    /// </summary>
    [JsonProperty("recordsTotal")]
    public int RecordsTotal { get; set; }

    /// <summary>
    /// Total records, after filtering (i.e. the total number of records after filtering has been applied - not just the number of records being returned for this page of data).
    /// </summary>
    [JsonProperty("recordsFiltered")]
    public int RecordsFiltered { get; set; }

    /// <summary>
    /// The data to be displayed in the table.
    /// This is an array of data source objects, one for each row, which will be used by DataTables.
    /// Note that this parameter's name can be changed using the ajax option's dataSrc property.
    /// </summary>
    [JsonProperty("data")]
    public IEnumerable<T> Data { get; set; }

    /// <summary>
    /// Optional: If an error occurs during the running of the server-side processing script, you can inform the user of this error by passing back the error message to be displayed using this parameter.
    /// Do not include if there is no error.
    /// </summary>
    [JsonProperty("error", NullValueHandling = NullValueHandling.Ignore)]
    public string Error { get; set; }

    public string PartialView { get; set; }
}

/// <summary>
/// The additional columns that you can send to jQuery DataTables for automatic processing.
/// </summary>
public abstract class DtRow
{
    /// <summary>
    /// Set the ID property of the dt-tag tr node to this value
    /// </summary>
    [JsonProperty("DT_RowId")]
    public virtual string DtRowId => null;

    /// <summary>
    /// Add this class to the dt-tag tr node
    /// </summary>
    [JsonProperty("DT_RowClass")]
    public virtual string DtRowClass => null;

    /// <summary>
    /// Add the data contained in the object to the row using the jQuery data() method to set the data, which can also then be used for later retrieval (for example on a click event).
    /// </summary>
    [JsonProperty("DT_RowData")]
    public virtual object DtRowData => null;

    /// <summary>
    /// Add the data contained in the object to the row dt-tag tr node as attributes.
    /// The object keys are used as the attribute keys and the values as the corresponding attribute values.
    /// This is performed using using the jQuery param() method.
    /// Please note that this option requires DataTables 1.10.5 or newer.
    /// </summary>
    [JsonProperty("DT_RowAttr")]
    public virtual object DtRowAttr => null;
}

/// <summary>
/// The parameters sent by jQuery DataTables in AJAX queries.
/// </summary>
public class DtParameters
{
    /// <summary>
    /// Draw counter.
    /// This is used by DataTables to ensure that the Ajax returns from server-side processing requests are drawn in sequence by DataTables (Ajax requests are asynchronous and thus can return out of sequence).
    /// This is used as part of the draw return parameter (see below).
    /// </summary>
    public int Draw { get; set; }

    /// <summary>
    /// An array defining all columns in the table.
    /// </summary>
    public DtColumn[] Columns { get; set; }

    /// <summary>
    /// An array defining how many columns are being ordering upon - i.e. if the array length is 1, then a single column sort is being performed, otherwise a multi-column sort is being performed.
    /// </summary>
    public DtOrder[] Order { get; set; }

    /// <summary>
    /// Paging first record indicator.
    /// This is the start point in the current data set (0 index based - i.e. 0 is the first record).
    /// </summary>
    public int Start { get; set; }

    /// <summary>
    /// Number of records that the table can display in the current draw.
    /// It is expected that the number of records returned will be equal to this number, unless the server has fewer records to return.
    /// Note that this can be -1 to indicate that all records should be returned (although that negates any benefits of server-side processing!)
    /// </summary>
    public int Length { get; set; }

    /// <summary>
    /// Global search value. To be applied to all columns which have searchable as true.
    /// </summary>
    public DtSearch Search { get; set; }

    /// <summary>
    /// Custom column that is used to further sort on the first Order column.
    /// </summary>
    public string SortOrder => Columns != null && Order != null && Order.Length > 0
        ? (Columns[Order[0].Column].Data +
           (Order[0].Dir == DtOrderDir.Desc ? " " + Order[0].Dir : string.Empty))
        : null;

    /// <summary>
    /// For Posting Additional Parameters to Server
    /// </summary>
    public IEnumerable<string> AdditionalValues { get; set; }

}

/// <summary>
/// A jQuery DataTables column.
/// </summary>
public class DtColumn
{
    /// <summary>
    /// Column's data source, as defined by columns.data.
    /// </summary>
    public string Data { get; set; }

    /// <summary>
    /// Column's name, as defined by columns.name.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Flag to indicate if this column is searchable (true) or not (false). This is controlled by columns.searchable.
    /// </summary>
    public bool Searchable { get; set; }

    /// <summary>
    /// Flag to indicate if this column is orderable (true) or not (false). This is controlled by columns.orderable.
    /// </summary>
    public bool Orderable { get; set; }

    /// <summary>
    /// Search value to apply to this specific column.
    /// </summary>
    public DtSearch Search { get; set; }
}

/// <summary>
/// An order, as sent by jQuery DataTables when doing AJAX queries.
/// </summary>
public class DtOrder
{
    /// <summary>
    /// Column to which ordering should be applied.
    /// This is an index reference to the columns array of information that is also submitted to the server.
    /// </summary>
    public int Column { get; set; }

    /// <summary>
    /// Ordering direction for this column.
    /// It will be dt-string asc or dt-string desc to indicate ascending ordering or descending ordering, respectively.
    /// </summary>
    public DtOrderDir Dir { get; set; }
}

/// <summary>
/// Sort orders of jQuery DataTables.
/// </summary>
public enum DtOrderDir
{
    Asc,
    Desc
}

/// <summary>
/// A search, as sent by jQuery DataTables when doing AJAX queries.
/// </summary>
public class DtSearch
{
    /// <summary>
    /// Global search value. To be applied to all columns which have searchable as true.
    /// </summary>
    public string Value { get; set; }

    /// <summary>
    /// true if the global filter should be treated as a regular expression for advanced searching, false otherwise.
    /// Note that normally server-side processing scripts will not perform regular expression searching for performance reasons on large data sets, but it is technically possible and at the discretion of your script.
    /// </summary>
    public bool Regex { get; set; }
}

Here is my empty Table: Basic Datatable features have been applied

Below is the error I am getting while trying to load the data: This is the error I am getting when trying to load data

Where am I getting it wrong?

Here is where the exception is occuring: enter image description here

@HMZ Implementation, I see empty records (images below):

I see this alert The Alert

Stuck on processing Stuck on processing

when number of rows is changed, I see this empty table: Empty table

3
Please show the console error not the alert, and make sure to include any exception on the server side.HMZ
@HMZ: I have updated the question and posted where an exception is first occuringRussell Chidhakwa
You need a custom model binder for this. I'll post a working example.HMZ
@HMZ: I am looking forward to that. I really appreciate your timeRussell Chidhakwa
Hi @RussellChidhakwa , can you please provide the link of the tutorialAbrar Jahin

3 Answers

4
votes

I have found the issue. I was lacking a JsonSerializer and I added it to my Startup.cs class.

This method works fine:

[HttpPost]
        public async Task<IActionResult> LoadTable([FromBody] DtParameters dtParameters)
        {
            var searchBy = dtParameters.Search?.Value;

            // if we have an empty search then just order the results by Id ascending
            var orderCriteria = "Id";
            var orderAscendingDirection = true;

            if (dtParameters.Order != null)
            {
                // in this example we just default sort on the 1st column
                orderCriteria = dtParameters.Columns[dtParameters.Order[0].Column].Data;
                orderAscendingDirection = dtParameters.Order[0].Dir.ToString().ToLower() == "asc";
            }

            var result = context.TestRegisters.AsQueryable();

            if (!string.IsNullOrEmpty(searchBy))
            {
                result = result.Where(r => r.Name != null && r.Name.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.FirstSurname != null && r.FirstSurname.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.SecondSurname != null && r.SecondSurname.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.Street != null && r.Street.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.Phone != null && r.Phone.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.ZipCode != null && r.ZipCode.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.Country != null && r.Country.ToUpper().Contains(searchBy.ToUpper()) ||
                                           r.Notes != null && r.Notes.ToUpper().Contains(searchBy.ToUpper()));
            }

            result = orderAscendingDirection ? result.OrderByDynamic(orderCriteria, DtOrderDir.Asc) : result.OrderByDynamic(orderCriteria, DtOrderDir.Desc);

            // now just get the count of items (without the skip and take) - eg how many could be returned with filtering
            var filteredResultsCount = await result.CountAsync();
            var totalResultsCount = await context.TestRegisters.CountAsync();

            return Json(new DtResult<TestRegister>
            {
                Draw = dtParameters.Draw,
                RecordsTotal = totalResultsCount,
                RecordsFiltered = filteredResultsCount,
                Data = await result
                    .Skip(dtParameters.Start)
                    .Take(dtParameters.Length)
                    .ToListAsync()
            });
        }

These DtParameters are correct:

public class DtResult<T>
    {
        /// <summary>
        /// The draw counter that this object is a response to - from the draw parameter sent as part of the data request.
        /// Note that it is strongly recommended for security reasons that you cast this parameter to an integer, rather than simply echoing back to the client what it sent in the draw parameter, in order to prevent Cross Site Scripting (XSS) attacks.
        /// </summary>
        [JsonProperty("draw")]
        public int Draw { get; set; }

        /// <summary>
        /// Total records, before filtering (i.e. the total number of records in the database)
        /// </summary>
        [JsonProperty("recordsTotal")]
        public int RecordsTotal { get; set; }

        /// <summary>
        /// Total records, after filtering (i.e. the total number of records after filtering has been applied - not just the number of records being returned for this page of data).
        /// </summary>
        [JsonProperty("recordsFiltered")]
        public int RecordsFiltered { get; set; }

        /// <summary>
        /// The data to be displayed in the table.
        /// This is an array of data source objects, one for each row, which will be used by DataTables.
        /// Note that this parameter's name can be changed using the ajax option's dataSrc property.
        /// </summary>
        [JsonProperty("data")]
        public IEnumerable<T> Data { get; set; }

        /// <summary>
        /// Optional: If an error occurs during the running of the server-side processing script, you can inform the user of this error by passing back the error message to be displayed using this parameter.
        /// Do not include if there is no error.
        /// </summary>
        [JsonProperty("error", NullValueHandling = NullValueHandling.Ignore)]
        public string Error { get; set; }

        public string PartialView { get; set; }
    }

    /// <summary>
    /// The additional columns that you can send to jQuery DataTables for automatic processing.
    /// </summary>
    public abstract class DtRow
    {
        /// <summary>
        /// Set the ID property of the dt-tag tr node to this value
        /// </summary>
        [JsonProperty("DT_RowId")]
        public virtual string DtRowId => null;

        /// <summary>
        /// Add this class to the dt-tag tr node
        /// </summary>
        [JsonProperty("DT_RowClass")]
        public virtual string DtRowClass => null;

        /// <summary>
        /// Add the data contained in the object to the row using the jQuery data() method to set the data, which can also then be used for later retrieval (for example on a click event).
        /// </summary>
        [JsonProperty("DT_RowData")]
        public virtual object DtRowData => null;

        /// <summary>
        /// Add the data contained in the object to the row dt-tag tr node as attributes.
        /// The object keys are used as the attribute keys and the values as the corresponding attribute values.
        /// This is performed using using the jQuery param() method.
        /// Please note that this option requires DataTables 1.10.5 or newer.
        /// </summary>
        [JsonProperty("DT_RowAttr")]
        public virtual object DtRowAttr => null;
    }

    /// <summary>
    /// The parameters sent by jQuery DataTables in AJAX queries.
    /// </summary>
    public class DtParameters
    {
        //public DtParameters()
        //{
        //    Search = new DtSearch();
        //    Search.Value = "";
        //}

        /// <summary>
        /// Draw counter.
        /// This is used by DataTables to ensure that the Ajax returns from server-side processing requests are drawn in sequence by DataTables (Ajax requests are asynchronous and thus can return out of sequence).
        /// This is used as part of the draw return parameter (see below).
        /// </summary>
        public int Draw { get; set; }

        /// <summary>
        /// An array defining all columns in the table.
        /// </summary>
        public DtColumn[] Columns { get; set; }

        /// <summary>
        /// An array defining how many columns are being ordering upon - i.e. if the array length is 1, then a single column sort is being performed, otherwise a multi-column sort is being performed.
        /// </summary>
        public DtOrder[] Order { get; set; }

        /// <summary>
        /// Paging first record indicator.
        /// This is the start point in the current data set (0 index based - i.e. 0 is the first record).
        /// </summary>
        public int Start { get; set; }

        /// <summary>
        /// Number of records that the table can display in the current draw.
        /// It is expected that the number of records returned will be equal to this number, unless the server has fewer records to return.
        /// Note that this can be -1 to indicate that all records should be returned (although that negates any benefits of server-side processing!)
        /// </summary>
        public int Length { get; set; }

        /// <summary>
        /// Global search value. To be applied to all columns which have searchable as true.
        /// </summary>
        public DtSearch Search { get; set; }

        /// <summary>
        /// Custom column that is used to further sort on the first Order column.
        /// </summary>
        public string SortOrder => Columns != null && Order != null && Order.Length > 0
            ? (Columns[Order[0].Column].Data +
               (Order[0].Dir == DtOrderDir.Desc ? " " + Order[0].Dir : string.Empty))
            : null;

        /// <summary>
        /// For Posting Additional Parameters to Server
        /// </summary>
        public IEnumerable<string> AdditionalValues { get; set; }

    }

    /// <summary>
    /// A jQuery DataTables column.
    /// </summary>
    public class DtColumn
    {
        /// <summary>
        /// Column's data source, as defined by columns.data.
        /// </summary>
        public string Data { get; set; }

        /// <summary>
        /// Column's name, as defined by columns.name.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Flag to indicate if this column is searchable (true) or not (false). This is controlled by columns.searchable.
        /// </summary>
        public bool Searchable { get; set; }

        /// <summary>
        /// Flag to indicate if this column is orderable (true) or not (false). This is controlled by columns.orderable.
        /// </summary>
        public bool Orderable { get; set; }

        /// <summary>
        /// Search value to apply to this specific column.
        /// </summary>
        public DtSearch Search { get; set; }
    }

    /// <summary>
    /// An order, as sent by jQuery DataTables when doing AJAX queries.
    /// </summary>
    public class DtOrder
    {
        /// <summary>
        /// Column to which ordering should be applied.
        /// This is an index reference to the columns array of information that is also submitted to the server.
        /// </summary>
        public int Column { get; set; }

        /// <summary>
        /// Ordering direction for this column.
        /// It will be dt-string asc or dt-string desc to indicate ascending ordering or descending ordering, respectively.
        /// </summary>
        public DtOrderDir Dir { get; set; }
    }

    /// <summary>
    /// Sort orders of jQuery DataTables.
    /// </summary>
    public enum DtOrderDir
    {
        Asc,
        Desc
    }

    /// <summary>
    /// A search, as sent by jQuery DataTables when doing AJAX queries.
    /// </summary>
    public class DtSearch
    {
        /// <summary>
        /// Global search value. To be applied to all columns which have searchable as true.
        /// </summary>
        public string Value { get; set; }

        /// <summary>
        /// true if the global filter should be treated as a regular expression for advanced searching, false otherwise.
        /// Note that normally server-side processing scripts will not perform regular expression searching for performance reasons on large data sets, but it is technically possible and at the discretion of your script.
        /// </summary>
        public bool Regex { get; set; }
    }

This Datatable configuration is correct:

$(document).ready(function () {
    $("#demoTable").DataTable({
        // Design Assets
        stateSave: true,
        autoWidth: true,
        // ServerSide Setups
        processing: true,
        serverSide: true,
        // Paging Setups
        paging: true,
        // Searching Setups
        searching: { regex: true },
        // Ajax Filter
        ajax: {
            url: "/TestRegisters/LoadTable",
            type: "POST",
            contentType: "application/json",
            dataType: "json",
            data: function (d) {
                return JSON.stringify(d);
            }
        },
        // Columns Setups
        columns: [
            { data: "id" },
            { data: "name" },
            { data: "firstSurname" },
            { data: "secondSurname" },
            { data: "street" },
            { data: "phone" },
            { data: "zipCode" },
            { data: "country" },
            { data: "notes" },
            { data: "creationDate" }
        ]
    });
});

THIS IS THE SINGLE LINE OF CODE I WAS MISSING FROM MY STARTUP TO MAKE IT WORK:

 services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));

After adding the JsonSerializer, I got this result: result

4
votes

Something need to be improved in your code:

  1. DataTable in asp.net core render the json data with a lowercase initial letter by default,so you need to change like below:

    columns: [ { data: "applicationId" }, { data: "firstname" }, { data: "surname" }, { data: "applicationFor" }, { data: "submissionDate" } ]

  2. The default DataTable contains the paging,sorting and searching.

  3. When i testing your code,it would get the following error message in the frontend(You could press F12 in browser to check the error message).Not sure whether you would get this error because the version of DataTable may be different:

    TypeError: Cannot set property ‘nTf’ of undefined

I search the error and conclude the result that it is caused by number of th elements in the table footer differs from number of th elements in the table header.But I find the numbers of thead and tfoot are the same in your code.

A bit different from the traditional DataTable is that you have empty th in both end of the thead and tfoot.So I have a try,then it works when i remove the th in tfoot but maintain the th in thead:

<thead>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.ApplicationId)
        </th>
        ...
        <th></th>
    </tr>
</thead>
<tfoot>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.ApplicationId)
        </th>
        ....
        @*<th></th>*@  //remove this...
    </tr>
</tfoot>

Here is a working demo like below:

Model:

public class StudentApplications
{
    public int ApplicationId { get; set; }
    public string Firstname { get; set; }
    public string Surname { get; set; }
    public string ApplicationFor { get; set; }
    public string SubmissionDate { get; set; }
}

View:

@model IEnumerable<StudentApplications>
<table id="custom-datatable" class="mb-5 display table table-bordered" style="width:100%">
     <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.ApplicationId)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Firstname)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Surname)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.ApplicationFor)
            </th>
            <th>
                Date
            </th>
            <th></th>
        </tr>
    </thead>
    <tfoot>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.ApplicationId)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Firstname)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Surname)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.ApplicationFor)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.SubmissionDate)
            </th>
            @*<th></th>*@
        </tr>
    </tfoot>
</table>
@section Scripts
{
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.20/css/jquery.dataTables.css">
    <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.js"></script>
    <script>
        $(document).ready(function () {
            $('#custom-datatable').DataTable({
                ajax: {
                    url: "/Home/data",
                    type:"POST"
                },
                columns: [
                    { data: "applicationId" },
                    { data: "firstname" },
                    { data: "surname" },
                    { data: "applicationFor" },
                    { data: "submissionDate" }
                ]

            });

        });
    </script>
}

Controller:

[HttpPost]
public ActionResult Data()
{
    var model = new List<StudentApplications>()
    {
        new StudentApplications(){ ApplicationId=1, ApplicationFor="aa", Firstname="Rena", SubmissionDate="2020-8-9", Surname="Ni"},
        new StudentApplications(){ ApplicationId=2, ApplicationFor="bb", Firstname="Sherry", SubmissionDate="2020-10-7", Surname="Chen"},
        new StudentApplications(){ ApplicationId=3, ApplicationFor="cc", Firstname="Xing", SubmissionDate="2020-5-10", Surname="Zou"},
        new StudentApplications(){ ApplicationId=4, ApplicationFor="dd", Firstname="a", SubmissionDate="2020-5-10", Surname="Zou"},
        new StudentApplications(){ ApplicationId=5, ApplicationFor="ee", Firstname="b", SubmissionDate="2020-5-10", Surname="sd"},
        new StudentApplications(){ ApplicationId=6, ApplicationFor="ff", Firstname="c", SubmissionDate="2020-5-10", Surname="df"},
        new StudentApplications(){ ApplicationId=7, ApplicationFor="gg", Firstname="d", SubmissionDate="2020-5-10", Surname="Zdfgou"},
        new StudentApplications(){ ApplicationId=8, ApplicationFor="hh", Firstname="e", SubmissionDate="2020-5-10", Surname="dfg"},
        new StudentApplications(){ ApplicationId=9, ApplicationFor="ii", Firstname="f", SubmissionDate="2020-5-10", Surname="dfg"},
        new StudentApplications(){ ApplicationId=10, ApplicationFor="jj", Firstname="g", SubmissionDate="2020-5-10", Surname="vbc"},
        new StudentApplications(){ ApplicationId=11, ApplicationFor="kk", Firstname="h", SubmissionDate="2020-5-10", Surname="hj"},
        new StudentApplications(){ ApplicationId=12, ApplicationFor="ll", Firstname="i", SubmissionDate="2020-5-10", Surname="hjk"},
        new StudentApplications(){ ApplicationId=13, ApplicationFor="mm", Firstname="j", SubmissionDate="2020-5-10", Surname="tyu"},
    };
    return Json(new {Data = model});
}

Startup.cs:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    public IConfiguration Configuration { get; }
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();                
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

Result:

enter image description here

3
votes

This is a working example of Datatables server side processing. This is an example and may need modification to work in your application.

Code

Datatables table parameters:

[ModelBinder(BinderType = typeof(DataTableModelBinder))]
public class DataTablesResult
{
    public int Draw { get; set; }
    public int Start { get; set; }
    public int Length { get; set; }
    public Search Search { get; set; }
    public List<ColumnOrder> Order { get; set; }
    public List<Column> Columns { get; set; }
}

public class Search
{
    public string Value { get; set; }
    public bool Regex { get; set; }
}

public class Column
{
    public string Data { get; set; }
    public string Name { get; set; }
    public bool Searchable { get; set; }
    public bool Orderable { get; set; }
    public Search Search { get; set; }
}

public class ColumnOrder
{
    public int Column { get; set; }
    public string Dir { get; set; }
}

A custom model binder in order to properly bind your table parameters with DataTablesResult on the server side:

public class DataTableModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var request = bindingContext.HttpContext.Request;

        // Retrieve request data
        var draw = Convert.ToInt32(request.Query["draw"]);
        var start = Convert.ToInt32(request.Query["start"]);
        var length = Convert.ToInt32(request.Query["length"]);

        // Search
        var search = new Search
        {
            Value = request.Query["search[value]"],
            Regex = Convert.ToBoolean(request.Query["search[regex]"])
        };

        // Order
        var o = 0;
        var order = new List<ColumnOrder>();
        while (!StringValues.IsNullOrEmpty(request.Query["order[" + o + "][column]"]))
        {
            order.Add(new ColumnOrder
            {
                Column = Convert.ToInt32(request.Query["order[" + o + "][column]"]),
                Dir = request.Query["order[" + o + "][dir]"]
            });
            o++;
        }

        // Columns
        var c = 0;
        var columns = new List<Column>();
        while (!StringValues.IsNullOrEmpty(request.Query["columns[" + c + "][name]"]))
        {
            columns.Add(new Column
            {
                Data = request.Query["columns[" + c + "][data]"],
                Name = request.Query["columns[" + c + "][name]"],
                Orderable = Convert.ToBoolean(request.Query["columns[" + c + "][orderable]"]),
                Searchable = Convert.ToBoolean(request.Query["columns[" + c + "][searchable]"]),
                Search = new Search
                {
                    Value = request.Query["columns[" + c + "][search][value]"],
                    Regex = Convert.ToBoolean(request.Query["columns[" + c + "][search][regex]"])
                }
            });
            c++;
        }

        var result = new DataTablesResult
        {
            Draw = draw,
            Start = start,
            Length = length,
            Search = search,
            Order = order,
            Columns = columns
        };

        bindingContext.Result = ModelBindingResult.Success(result);
        return Task.CompletedTask;
    }
}

Server side action:

[HttpGet]
public async Task<IActionResult> DataAjax(DataTablesResult tableParams)
{
    var query = dataWrapper.YourDataSet.GetAll();

    var totalCount = await query.CountAsync();

    if (tableParams.Search != null)
        query = query.Where(c => c.Firstname.Contains(tableParams.Search.Value)
        || c.Surname.Contains(tableParams.Search.Value));
       //Other search queries goes here

    if (tableParams.Order.Count > 0)
    {
        if (tableParams.Order[0].Dir == "asc")
            query = query.OrderBy(c => EF.Property<string>(c, tableParams.Columns[tableParams.Order[0].Column].Name));
        else
            query = query.OrderByDescending(c => EF.Property<string>(c, tableParams.Columns[tableParams.Order[0].Column].Name));
    }
    //In this example multi order is disabled so we can order by any column dynamically

    var result = await query.AsNoTracking().Select(E => new { Count = query.Count(), E = E }).Skip(tableParams.Start).Take(tableParams.Length).ToListAsync();
    var pureData = result.Select(E => E.E);

    if (result.Count == 0)
        return Ok(new { tableParams.Draw, recordsTotal = 0, recordsFiltered = 0, data = Enumerable.Empty<string>() });

    return Ok(new { tableParams.Draw, recordsTotal = totalCount, recordsFiltered = result.Select(c => c.Count).FirstOrDefault(), data = pureData });
}

Datatables configuration:

var table = $("#dataTable").DataTable({
    serverSide: true,
    processing: true,
    ajax: {
        url: "/Controller/DataAjax",
        method: "GET",
        dataType: "json"
    },
    select: true,
    orderMulti: false, //Multi column order is disabled
    columnDefs: [{
        targets: [], //Here you can specify targets for cell ellipsis
        render: $.fn.dataTable.render.ellipsis(10, true)
    },
    {
        targets: [], //Disable search on targeted columns
        searchable: false
    },
    {
        targets: [], //Disable order on targeted columns
        orderable: false
    }],
    columns: [
        { "name": "ApplicationId" } //Here your column names should match your db columns in order to have dynamic order.
    ]
});