asp.net-mvcdatatables

JQuery Datatable returning null when using Search


I posted the same question on the Datatables forum but they could not help me: https://datatables.net/forums/discussion/80946/searchpanes-not-working-correctly-with-razor-mvc#latest

I investigated a little bit more after posting so I write it down here again.

I am using Datatables with a .Net Framework 4.8 MVC application. The data (or part of it) of the table is lost when searching and submitting the Razor page to the Controller.

The problem is in a large legacy application but I created a simple app to pin down the problem. Here is the Razor page:


@using MVCTestbench.Models
@using System.Collections.Generic

@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/bundles/datatable")

@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
@Styles.Render("~/Styles/datatable")

@{
    ViewBag.Title = "Home Page";
}

<script type="text/javascript" charset="utf-8">
    $(document).ready(function () {

        new DataTable('#CaseTable', {
            layout: {
                top1: {
                    searchPanes: {
                        viewTotal: true
                    }
                }
            }
        });
    });
</script>

@using (Html.BeginForm("Post", "Home", FormMethod.Post, new { id = "frmCases"}))
{
    <input type="submit" value="Save" />
    <br />

    <table id="CaseTable">
        <thead>
            <tr>
                <th>Id</th>
                <th>Case</th>
            </tr>
        </thead>
        <tbody>
            @for (int i = 0; i < Model.CaseList.Count; i++)
            {
                <tr>
                    <td>
                        @Model.CaseList[i].Id @Html.HiddenFor(m => m.CaseList[i].Id)
                    </td>
                    <td>
                        @Model.CaseList[i].Name @Html.HiddenFor(m => m.CaseList[i].Name)
                    </td>
                </tr>
            }
        </tbody>
    </table>
}

This is the ViewModel:

using System.Collections.Generic;

namespace MVCTestbench.Models
{
    public class CaseViewModel
    {
        public List<Case> CaseList { get; set; } = new List<Case>();
    }

    public class Case
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }
}

Controller code is this:

using MVCTestbench.Models;
using System.Web.Mvc;

namespace MVCTestbench.Controllers
{
    public class HomeController : Controller
    {
        private CaseViewModel ViewModel
        {
            get
            {
                var model = new CaseViewModel();
                model.CaseList.Add(new Case { Id = "1", Name = "Text 1" });
                model.CaseList.Add(new Case { Id = "2", Name = "Text 2" });
                model.CaseList.Add(new Case { Id = "3", Name = "Case 3" });
                return model;
            }
        }

        public ActionResult Index()
        {
            return View(ViewModel);
        }

        [HttpPost]
        public ActionResult Post(CaseViewModel model)
        {
            // model.CaseList is null here if I searched by "Case"
            return View("Index", ViewModel);
        }

    }
}

When searching by "Case" like in the screenshot, the model.Caselist is null! When searching by "Text", it works ok (model.Caselist contains two rows). enter image description here


Solution

  • Your Razor form relies on hidden inputs (@Html.HiddenFor) to submit values back to the controller. However, when DataTables applies a filter or search, it removes the hidden inputs for non-visible (filtered-out) rows from the DOM. As a result, those filtered-out rows are not posted back to your controller, so CaseList becomes incomplete or null.

    When you search for "Case", only the "Case 3" row remains visible in the DOM. If that row does not match your expectations or somehow doesn’t preserve the inputs properly, then the CaseList binding may be empty or null.

    Option 1: Use JavaScript to Collect Data Before Submit

    Before the form is submitted, iterate through all rows (visible and hidden), and ensure their data is included in the form. Here's how:

    <script type="text/javascript">
        let dataTable;
    
        $(document).ready(function () {
            dataTable = new DataTable('#CaseTable', {
                layout: {
                    top1: {
                        searchPanes: {
                            viewTotal: true
                        }
                    }
                }
            });
    
            $('#frmCases').on('submit', function () {
                // Clear any previously added inputs
                $('#frmCases').find('.dt-hidden-input').remove();
    
                // Get all rows, not just filtered
                dataTable.rows().every(function () {
                    const data = this.data();
                    const rowNode = this.node();
                    const $row = $(rowNode);
    
                    const id = $row.find('input[name*=".Id"]').val();
                    const name = $row.find('input[name*=".Name"]').val();
    
                    // Add hidden inputs manually
                    if (id !== undefined && name !== undefined) {
                        const index = this.index();
                        $('<input>').attr({
                            type: 'hidden',
                            name: `CaseList[${index}].Id`,
                            value: id,
                            class: 'dt-hidden-input'
                        }).appendTo('#frmCases');
    
                        $('<input>').attr({
                            type: 'hidden',
                            name: `CaseList[${index}].Name`,
                            value: name,
                            class: 'dt-hidden-input'
                        }).appendTo('#frmCases');
                    }
                });
    
                return true;
            });
        });
    </script>
    

    Option 2: Use Ajax to Submit Instead

    If you can, use AJAX to send the data manually from DataTables, rather than relying on Razor's form binding.

    But for Razor MVC and your current model binding, Option 2 is the most straightforward fix.