30

In Datatables 1.10 the ajax server side parameters changed from

public class DataTableParamModel
{ 
    public string sEcho{ get; set; }
    public string sSearch{ get; set; }
    public int iDisplayLength{ get; set; }
    public int iDisplayStart{ get; set; }
    public int iColumns{ get; set; }
    public int iSortingCols{ get; set; }
    public string sColumns{ get; set; }
}

to (API Here http://datatables.net/manual/server-side)

columns[i][data]    
columns[i][name]    
columns[i][orderable]
columns[i][search][regex]
columns[i][search][value]   
columns[i][searchable]  
...     
draw    
length  
order[i][column]    
order[i][dir]
... 
search[regex]
search[value]   
start

Some are easy to bind

public class DataTableParamModel
{ 
    public string draw { get; set; }
    public int length{ get; set; }
    public int start { get; set; }
}

But the new array format looks tricky.

What is the new appropriate model to map the new parameter format?

6 Answers 6

36

Here is a model binder and class that will bind these new parameters...

Nuget Package: https://www.nuget.org/packages/Wetware.DataTables

Parameter Model:

[ModelBinder(typeof(DTModelBinder))]
public class DTParameterModel
{
    /// <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 
    /// </summary>
    public int Draw { 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 for the table
    /// </summary>
    public DTSearch Search { get; set; }

    /// <summary>
    /// Collection of all column indexes and their sort directions
    /// </summary>
    public IEnumerable<DTOrder> Order { get; set; }

    /// <summary>
    /// Collection of all columns in the table
    /// </summary>
    public IEnumerable<DTColumn> Columns { get; set; }
}

/// <summary>
/// Represents search values entered into the table
/// </summary>
public sealed 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; }
}

/// <summary>
/// Represents a column and it's order direction
/// </summary>
public sealed 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 asc or desc to indicate ascending
    /// ordering or descending ordering, respectively
    /// </summary>
    public string Dir { get; set; }
}

/// <summary>
/// Represents an individual column in the table
/// </summary>
public sealed class DTColumn
{
    /// <summary>
    /// Column's data source
    /// </summary>
    public string Data { get; set; }

    /// <summary>
    /// Column's name
    /// </summary>
    public string Name { get; set; }

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

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

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

Model Binder:

/// <summary>
/// Model Binder for DTParameterModel (DataTables)
/// </summary>
public class DTModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        base.BindModel(controllerContext, bindingContext);
        var request = controllerContext.HttpContext.Request;
        // Retrieve request data
        var draw = Convert.ToInt32(request["draw"]);
        var start = Convert.ToInt32(request["start"]);
        var length = Convert.ToInt32(request["length"]);
        // Search
        var search = new DTSearch
        {
            Value = request["search[value]"],
            Regex = Convert.ToBoolean(request["search[regex]"])
        };
        // Order
        var o = 0;
        var order = new List<DTOrder>();
        while (request["order[" + o + "][column]"] != null)
        {
            order.Add(new DTOrder
            {
                Column = Convert.ToInt32(request["order[" + o + "][column]"]),
                Dir = request["order[" + o + "][dir]"]
            });
            o++;
        }
        // Columns
        var c = 0;
        var columns = new List<DTColumn>();
        while (request["columns[" + c + "][name]"] != null)
        {
            columns.Add(new DTColumn
            {
                Data = request["columns[" + c + "][data]"],
                Name = request["columns[" + c + "][name]"],
                Orderable = Convert.ToBoolean(request["columns[" + c + "][orderable]"]),
                Searchable = Convert.ToBoolean(request["columns[" + c + "][searchable]"]),
                Search = new DTSearch
                {
                    Value = request["columns[" + c + "][search][value]"],
                    Regex = Convert.ToBoolean(request["columns[" + c + "][search][regex]"])
                }
            });
            c++;
        }

        return new DTParameterModel
        {
            Draw = draw,
            Start = start,
            Length = length,
            Search = search,
            Order = order,
            Columns = columns
        };
    }
}

Usage:

MyController.cs

public JsonResult DataTablesList(DTParameterModel model)
{
    ...
}

MVC6

If you're going to MVC6 you no longer need the model binder as MVC6 includes a JQueryFormValueProvider into the default model binder that can bind these values.

The model classes themselves may still be useful, however.

There is a bug to be fixed in 2.1.0 that doesn't allow binding for HttpGet but still works for HttpPost

Sign up to request clarification or add additional context in comments.

12 Comments

This code is terrific and saved me a huge slice of time! You'll need to add something to application startup code, too: ModelBinders.Binders.Add(typeof(DTParameterModel), new DTModelBinder());
@user2320070 You're welcome. I've added a usage section as well.
+1, great code! My two cents, for better portability, you can implement this without altering Global.asax.cs: Right above public class DTParameterModel add [ModelBinder(typeof(DTModelBinder))]. Then, the MyController.cs method can be just: public JsonResult DataTablesList(DTParameterModel model). And btw, your DTColumn class is missing one property: public bool Searchable { get; set; } (reference).
God Mode Enabled +100000
You sir are a life saver! been looking for this all day. Thank you!
|
7

Give this a try @shoe: datatables-mvc project: https://github.com/ALMMa/datatables-mvc

1 Comment

It works with The model binder, No issues at all. Now just to implement ordering with EF
5

I changed my javascript to use the legacy ajax params option which uses the old parameters to send to the server. This is done through $.fn.dataTable.ext.legacy.ajax = true; so now my code becomes something like...

$.fn.dataTable.ext.legacy.ajax = true;
var datatable = $('#data-table').DataTable({
    "processing": true,
    "serverSide": true,
    "ajax": "MyController/AjaxHandlerPaging",
    "pageLength": 25,
    "order": [[2, 'desc']],
    "columns": []
});

Comments

5

Know this post is 2 years old but to those who want to use this with ASP.Net Core MVC 6. This is the converted/ upgraded answer provided by @Shoe

Model Binder:

using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TrackingAndTraining.Models
{
    /// <summary>
    /// Model Binder for DTParameterModel (DataTables)
    /// </summary>
    public class DTModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {

            var request = bindingContext.ActionContext.HttpContext.Request.Form;
            // Retrieve request data
            var draw = Convert.ToInt32(request["draw"]);
            var start = Convert.ToInt32(request["start"]);
            var length = Convert.ToInt32(request["length"]);
            // Search
            var search = new DTSearch
            {
                Value = request["search[value]"],
                Regex = Convert.ToBoolean(request["search[regex]"])
            };
            // Order
            var o = 0;
            var order = new List<DTOrder>();
            while (!string.IsNullOrEmpty(request["order[" + o + "][column]"]))
            {
                order.Add(new DTOrder
                {
                    Column = Convert.ToInt32(request["order[" + o + "][column]"]),
                    Dir = request["order[" + o + "][dir]"]
                });
                o++;
            }
            // Columns
            var c = 0;
            var columns = new List<DTColumn>();
            while (!string.IsNullOrEmpty(request["columns[" + c + "][name]"]))
            {
                columns.Add(new DTColumn
                {
                    Data = request["columns[" + c + "][data]"],
                    Name = request["columns[" + c + "][name]"],
                    Orderable = Convert.ToBoolean(request["columns[" + c + "][orderable]"]),
                    Searchable = Convert.ToBoolean(request["columns[" + c + "][searchable]"]),
                    Search = new DTSearch
                    {
                        Value = request["columns[" + c + "][search][value]"],
                        Regex = Convert.ToBoolean(request["columns[" + c + "][search][regex]"])
                    }
                });
                c++;
            }

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

            bindingContext.Result = ModelBindingResult.Success(result);

            return TaskCache.CompletedTask;
        }

    }
}

Parameter Model:

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TrackingAndTraining.Models
{
    [ModelBinder(BinderType = typeof(DTModelBinder))]
    public class DTParameterModel
    {
        /// <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 
        /// </summary>
        public int Draw { 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 for the table
        /// </summary>
        public DTSearch Search { get; set; }

        /// <summary>
        /// Collection of all column indexes and their sort directions
        /// </summary>
        public List<DTOrder> Order { get; set; }

        /// <summary>
        /// Collection of all columns in the table
        /// </summary>
        public List<DTColumn> Columns { get; set; }
    }

    /// <summary>
    /// Represents search values entered into the table
    /// </summary>
    public sealed 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; }
    }

    /// <summary>
    /// Represents a column and it's order direction
    /// </summary>
    public sealed 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 asc or desc to indicate ascending
        /// ordering or descending ordering, respectively
        /// </summary>
        public string Dir { get; set; }
    }

    /// <summary>
    /// Represents an individual column in the table
    /// </summary>
    public sealed class DTColumn
    {
        /// <summary>
        /// Column's data source
        /// </summary>
        public string Data { get; set; }

        /// <summary>
        /// Column's name
        /// </summary>
        public string Name { get; set; }

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

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

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

Again all credit goes to @Shoe for original post.

3 Comments

You shouldn't need a model binder for MVC6 since it now includes a provider that can translate these values. See here
I have used this functionality, but doesn't this require you maintain the names of fields sent, as property names in your object? And any special conditions would have to be placed in the getter of those properties. I prefer to use a model binder for this, may just be me.
The default model binder in mvc6 will bind the jquery format datatables uses just at the model binder I wrote for mvc5 would. You might still find the datatables model useful but if your intent is just to bind datatables you no longer need a custom binder like you did before.
4

I ran into the same issue when moving to 1.10. Basically, I changed my parameter class like this (getting rid of the unsupported parameters):

public class jQueryDataTableParamModel
{
    /// <summary>
    /// Request sequence number sent by DataTable,
    /// same value must be returned in response
    /// </summary>       
    public string draw { get; set; }

    /// <summary>
    /// Number of records that should be shown in table
    /// </summary>
    public int length { get; set; }

    /// <summary>
    /// First record that should be shown(used for paging)
    /// </summary>
    public int start { get; set; }
}

In my controller, I get the search value, sort order, and sort column like this:

        var searchString = Request["search[value]"];
        var sortColumnIndex = Convert.ToInt32(Request["order[0][column]"]);
        var sortDirection = Request["order[0][dir]"]; // asc or desc

Comments

0

The full server side binding implementation can be found here. I ran into a similar issue and this was how I went about solving it.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.