如何在 ASP.net Core 中实现 dataTables 服务器端分页/搜索/排序

Posted

技术标签:

【中文标题】如何在 ASP.net Core 中实现 dataTables 服务器端分页/搜索/排序【英文标题】:How to implement dataTables Server side Side Paging/Searching/Sorting in ASP.net Core 【发布时间】:2020-07-20 13:38:08 【问题描述】:

我正在尝试从 jQuery Datatable 的服务器端执行搜索、排序、分页,我已经写过 遵循代码,但我无法获取用于排序的参数,从数据表搜索到我的 MVC 控制器。 这是我的代码。

我的数据表看起来像...

<script>
    $(document).ready(function () 
        $("#newT").DataTable(
            ajax: 
                url: '@Url.Action("Prods", "NewTest")',
                method: 'post',
                dataType: 'json',
                dataSrc : ''
            ,
            columns: [
                
                    title: 'Id',
                    
                    data: 'id',
                    searchable: false,
                    sortable: false,
                    visible: false
                ,
                
                    title: 'Name',
                   
                    data: 'name',
                    render: function (data, type, row) 
                        return '<a id="' + row.id + '" href="javascript:void(0)" onclick="detailsClick(this)">' + data + '</a>'

                    
                ,
                
                ....Other Columns....
            ],
            serverSide: true,
            processing: true,
            language: 
                processing : "Please Wait ..."
            
        )
    )
    </script>

这是我的 MVC 控制器

public IActionResult Prods()
int draw = Convert.ToInt32(Request.Query["draw"]);
            int StartIndex = Convert.ToInt32(Request.Query["start"]);
            int PageSize = Convert.ToInt32(Request.Query["length"]);
            int SortCol = Convert.ToInt32(Request.Query["order[0][column]"]);
            string SortDir = Request.Query["order[0][dir]"];
            string SearchField = Request.Query["search[value]"].FirstOrDefault()?.Trim();
            
            ..... Further Implementation .....
             
            return new JsonResult(new
            
                data = filteredData,
                draw = Request.Query["draw"],
                recordsFiltered = filteredData.Count(),
                recordsTotal = 13
            
                    );

            

我在 Controller 中得到了空值,请帮帮我。

【问题讨论】:

【参考方案1】:

在 NuGet 中已经有几个用于 jquery 数据表的 .NET Core 实现。它具有将数据表请求连接到 MVC 所需的所有模型和绑定。

我用过这个:https://www.nuget.org/packages/DataTables.AspNet.AspNetCore/。

这是在 ASP.NET Core MVC 应用程序中使用它的方法:

1。安装 DataTables.js

使用您最喜欢的客户端包管理器安装 DataTables.js 库(以及它的依赖项 jQuery)。 DataTables 还附带couple styles,因此它们将拥有自己的包。你也需要得到它。

我用了npm,直接在package.json里面做了:


    "version": "1.0.0",
    ...,
    "dependencies": 
        ...,
        "jquery": "3.5.1",
        "datatables.net": "1.10.21",
        "datatables.net-bs4": "1.10.21"
    ,
    "devDependencies": 
        ...
    

这些包将被拉入您项目中的隐藏文件夹node_modules

2。将静态文件移动到 wwwroot

在 ASP.NET Core MVC 应用程序中,为了提供任何 JavaScript 或 css 文件,您需要在 Startup 类中注册 app.UseStaticFiles(),并使用任何 Task Runner 将文件移动到 wwwroot 文件夹。

namespace DL.NetCore.EmptySolution.Web.UI

    public class Startup
    
        ...

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        
            ...
            app.UseStaticFiles();
            ...
        
    

我使用了gulp,但你可以使用任何你想要的东西。我在项目中只有一个gulpfile.js,Visual Studio 的任务运行程序将获取它:

const bundleAndMinify = (srcFiles, destFileName, destPath, minifier) => 
    src(srcFiles)
        .pipe(concat(destFileName))
        .pipe(dest(destPath))
        .pipe(minifier())
        .pipe(rename( suffix: '.min' ))
        .pipe(dest(destPath));
;

const copyDatatablesScripts = done => 
    bundleAndMinify(
        [
            'node_modules/datatables.net/js/jquery.dataTables.js',
            'assets/js/dataTables.bootstrap4.custom.js'
        ],
        'jquery.dataTables.js',
        'wwwroot/js',
        terser
    );

    done();
;

const copyDatatablesStyles = done => 
    bundleAndMinify(
        [
            'node_modules/datatables.net-bs4/css/dataTables.bootstrap4.css',
            'assets/css/dataTables.bootstrap4.custom.css'
        ],
        'jquery.dataTables.css',
        'wwwroot/css',
        cssmin
    );

    done();
;

我进行了自定义设置,因为我想更改排序图标并添加搜索文件延迟扩展,因此我在资产文件夹中拥有自己的自定义 dataTables.bootstrap4.custom.jsassets/css/dataTables.bootstrap4.custom.css

但对于基本安装,您只需要:

脚本: node_modules/jquery/dist/jquery.js node_modules/datatables.net/js/jquery.dataTables.js node_modules/datatables.net-bs4/js/dataTables.bootstrap4.js 款式: node_modules/datatables.net-bs4/css/dataTables.bootstrap4.css

在 wwwroot 中拥有这些静态文件后,您可以将这些脚本和样式包含在要运行 DataTables.js 库的任何页面中:

@section css 
    <!-- jquery.dataTables styles -->
    <environment include="development">
        <link rel="stylesheet" href="~/css/jquery.dataTables.css" />
    </environment>
    <environment exclude="development">
        <link rel="stylesheet" href="~/css/jquery.dataTables.min.css" />
    </environment>


@section scripts 
    <!-- jquery.dataTables scripts -->
    <environment include="development">
        <script src="~/js/jquery.dataTables.js"></script>
    </environment>
    <environment exclude="development">
        <script src="~/js/jquery.dataTables.min.js"></script>
    </environment>

    ...

注意:在gulpfile.js 中,您可以看到我将脚本文件捆绑到一个名为jquery.dataTables.js 的单个文件中,并将样式打包到一个名为jquery.dataTables.css 的单个文件中。这就是为什么我在页面上只引用这两个文件的原因。

3。安装 DataTables NuGet 包

右键单击您的项目并打开 NuGet 管理器。搜索“DataTables.AspNet.AspNetCore”并单击安装。它也会自动下载其依赖项。

4。注册 DataTables NuGet 包

在您可以使用 NuGet 包之前,您需要注册它。这可能是您的错误的来源:

namespace DL.NetCore.EmptySolution.Web.UI

    public class Startup
    
        ...

        public void ConfigureServices(IServiceCollection services)
        
            services
                .AddRouting(options => options.LowercaseUrls = true)
                .AddControllersWithViews();

            services.RegisterDataTables();
        
    

它会为你添加模态绑定,这样你就不必自己手动绑定那么多DataTables请求参数了。

5。玩得开心!

现在,您可以使用 IDataTablesRequest 参数从 DataTables.js 库中简单地声明任何用作 AJAX 调用的 MVC 操作。这将捕获 Request.Query["start"]Request.Query["length"] 等内容。感谢 DataTables NuGet 包!

在你处理完数据后,你只需要创建一个DataTablesJsonResult()返回给客户端:

namespace DL.NetCore.EmptySolution.Web.UI.Controllers

    public class ProductController : Controller
    
        public IActionResult Index()
        
            return View();
        

        [HttpPost]
        public IActionResult GetList(IDataTablesRequest request)
        
            // Get products from your persistance store
            var products = GetFakeProducts();

            // Convert them into view models
            var rows = new List<ProductListRowViewModel>();
            // foreach product in products ...

            // Filter them
            var filteredRows = rows
                .AsQueryable()
                .GlobalFilterBy(request.Search, request.Columns);

            // Sort and paginate them
            var pagedRows = filteredRows
                .SortBy(request.Columns)
                .Skip(request.Start)
                .Take(request.Length);
            
            var response = DataTablesResponse.Create(request, rows.Count,
                filteredRows.Count(), pagedRows);

            return new DataTablesJsonResult(response);
        
    

您可以看到DataTables.js从客户端发送的所有信息都被捕获并转换为IDataTablesRequest,您可以获取任何您需要的信息:

@
    ViewBag.Title = "Products";


<h2>Products</h2>
...
<div class="table-responsive">
    <table id="table-products" class="table">
        ...
    </table>
</div>
...

@section scripts 
    ...

    <script type="text/javascript">
        $(function() 
            $('#table-products').dataTable(
                serverSide: true,
                ajax: 
                    url: '@Url.Action("getList", "product", new  area = "" )',
                    type: 'POST'
                ,
                processing: true,
                order: [1, 'asc'],
                columns: [...]
            ).fnSetFilteringDelay(1000);
        );
    </script>

截图

工作演示

https://github.com/davidliang2008/DL.NetCore.EmptySolution

奖励:应用 DataTables 过滤和排序的扩展方法

我还针对 IQueryable 集合创建了 2 个扩展方法来处理 DataTables.js 中的过滤和排序:

namespace DL.NetCore.EmptySolution.Web.Common.DataTables.Extensions

    public static class QueryableExtensions
    
        public static IQueryable<T> SortBy<T>(this IQueryable<T> source, 
            IEnumerable<IColumn> columns)
        
            Expression expression = source.Expression;
            bool firstTime = true;

            var sortedColumns = columns
                .Where(x => x.IsSortable && x.Sort != null)
                .OrderBy(x => x.Sort.Order);

            foreach (var sortedColumn in sortedColumns)
            
                var parameter = Expression.Parameter(typeof(T), "x");
                var selector = Expression.PropertyOrField(parameter, sortedColumn.Field);
                var lambda = Expression.Lambda(selector, parameter);
                var method = sortedColumn.Sort.Direction == SortDirection.Descending
                    ? firstTime ? "OrderByDescending" : "ThenByDescending"
                    : firstTime ? "OrderBy" : "ThenBy";

                expression = Expression.Call(
                    typeof(Queryable), 
                    method,
                    new Type[]  source.ElementType, selector.Type ,
                    expression, 
                    Expression.Quote(lambda)
                );

                firstTime = false;
            
            return firstTime 
                ? source
                : source.Provider.CreateQuery<T>(expression);
        

        public static IQueryable<T> GlobalFilterBy<T>(this IQueryable<T> source,
            ISearch search, IEnumerable<IColumn> columns)
        
            if (search == null || String.IsNullOrWhiteSpace(search.Value))
            
                return source;
            

            var searchableColumns = columns
                .Where(x => x.IsSearchable);
            if (!searchableColumns.Any())
            
                return source;
            

            Expression predicateBody = null;
            var parameter = Expression.Parameter(typeof(T), "x");

            foreach (var column in searchableColumns)
            
                // We want to build
                //  x.Field != default 
                //  && x.Field.ToString().IndexOf(search.Value, StringComparison.InvariantCultureIgnoreCase) >= 0
                // for each searchable column
                var selector = Expression.PropertyOrField(parameter, column.Field);

                var left = Expression.NotEqual(
                    selector,
                    Expression.Default(selector.Type)
                );

                var toString = Expression.Call(
                    selector,
                    selector.Type.GetMethod("ToString", System.Type.EmptyTypes)
                );

                var indexOf = Expression.Call(
                    toString,
                    typeof(string).GetMethod("IndexOf", new Type[]  typeof(string), typeof(StringComparison) ),
                    Expression.Constant(search.Value),
                    Expression.Constant(StringComparison.InvariantCultureIgnoreCase)
                );

                var right = Expression.GreaterThanOrEqual(
                    indexOf,
                    Expression.Constant(0)
                );

                var andExpression = Expression.AndAlso(left, right);

                predicateBody = predicateBody == null
                    ? andExpression
                    : Expression.OrElse(predicateBody, andExpression);
            

            if (predicateBody == null)
            
                return source;
            

            var lambda = Expression.Lambda<Func<T, bool>>(predicateBody, parameter);

            var whereCallExpression = Expression.Call(
                typeof(Queryable),
                "Where",
                new Type[]  source.ElementType ,
                source.Expression,
                lambda
            );

            return source.Provider.CreateQuery<T>(whereCallExpression);
        
    

它们非常方便地从 DataTables.js 中获取可搜索和可排序的列,动态构建表达式树并应用于您的源集合!

抱歉,这里太长了……

【讨论】:

谢谢,大卫,这将有很大帮助,但你能否提供任何有效的演示,我可以联系到我收到一个错误 :: InvalidOperationException: Could not create an instance of type 'DataTables.AspNet.Core.IDataTablesRequest'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the 'request' parameter a non-null default value. @MaulikDave:我正在做一个演示,但工作太忙了。会及时通知您。 @MaulikDave:对不起,我太忙了,但你来了!我已经用详细说明更新了我的答案,并在我的 Github 中提供了我工作演示的链接。玩得开心。 非常感谢您提供如此广泛的演示,这将有很大帮助,对此非常感谢。【参考方案2】:

我无法获取排序参数,从数据表搜索到我的 MVC 控制器。

如果你使用F12开发工具Network抓取到NewTest/Prodsaction方法的请求,你会发现服务端处理相关数据是通过表单数据传递的,而不是在url的查询字符串部分。

要在控制器操作中获取这些数据,您可以尝试:

int draw = Convert.ToInt32(Request.Form["draw"]);
int StartIndex = Convert.ToInt32(Request.Form["start"]);
int PageSize = Convert.ToInt32(Request.Form["length"]);
int SortCol = Convert.ToInt32(Request.Form["order[0][column]"]);
string SortDir = Request.Form["order[0][dir]"];
string SearchField = Request.Form["search[value]"].FirstOrDefault()?.Trim();

测试结果

【讨论】:

我知道这是旧版本并且有更新的版本,但是当我检查我的 Chrome 开发工具时,我看到像“搜索[对象对象]”和“列”这样发布的值[object object]”而不是您在屏幕截图中显示的内容。有谁知道为什么会这样?编辑:我应该补充一点,因为它们只是“[object object]”的字符串值,它们无法在后端解析。 上述问题的解决方案是我发现我们在使用 DataTables 测试的文件中使用了“jQuery.ajaxSettings.traditional = true”。删除/评论解决了发布值的问题。

以上是关于如何在 ASP.net Core 中实现 dataTables 服务器端分页/搜索/排序的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ASP.NET Core 2.2 中实现标识

如何在 ASP.net Core 中实现 dataTables 服务器端分页/搜索/排序

在 ASP.NET Core 2.1 Web API 中实现分页

如何在ASP NET Core中实现CORS跨域

使用 Swagger 在 ASP.NET Core 中实现 OAuth

在 ASP.Net Core 中实现 ADO 连接