ASP.NET 8 使用 DataTables.net – 第一部分 – 基础
一份关于在 ASP.NET 8 MVC 应用程序中使用 jQuery DataTables.net 组件的实用指南。
1 ASP.NET8 使用 jQuery DataTables.net
我一直在为我的新项目(ASP.NET 8、C#、MVC、Bootstrap 5、EF)寻找一个适合我的开发环境的免费表格组件。在许多地方,我看到了 jQuery DataTables.net 组件 [1] 的提及。在浏览了不同的文章后,我决定创建几个 原型(概念验证)应用程序 来评估它是否适合在我的专业项目中使用。这些文章是我评估的结果。
1.1 本系列文章
本系列文章包括:
- ASP.NET8 使用 DataTables.net – 第1部分 – 基础
- ASP.NET8 使用 DataTables.net – 第2部分 – 操作按钮
- ASP.NET8 使用 DataTables.net – 第3部分 – 状态保存
- ASP.NET8 使用 DataTables.net – 第4部分 – 多语言
- ASP.NET8 使用 DataTables.net – 第5部分 – 在 AJAX 中传递附加参数
- ASP.NET8 使用 DataTables.net – 第6部分 – 在 AJAX 中返回附加参数
- ASP.NET8 使用 DataTables.net – 第7部分 – 常规按钮
- ASP.NET8 使用 DataTables.net – 第8部分 – 选择行
- ASP.NET8 使用 DataTables.net – 第9部分 – 高级筛选器
2 最终结果
现在,让我们在 ASP.NET 8、C#、MVC、Bootstrap 5 环境中展示本文原型测试的结果。这是您将获得的内容:
您将获得一个外观漂亮的表格,它通过后台 AJAX 调用获取数据。您在图片中看到的表格本身是一个 jQuery 组件,您可以用 C# 编写后端处理。与图片中任何 绿色区域 的交互都会触发一个新的 AJAX 调用到 ASP.NET 服务器,并带上新参数,服务器会返回新数据集。
好处是您无需编写所有 HTML/JavaScript 来创建表格,这些由 DataTables.net 组件提供给您。当然,您需要学习组件的 API,并且在组件提供的 UI 选项方面会受到一些限制。
2.1 积极印象,但有所保留
我的印象总体是积极的,我将在我的项目中使用 DataTables.net 组件。其逻辑是,我需要这样一个组件来呈现我的表格,如果不用这个,我将需要开发我自己的版本,具有类似的功能来呈现我的表格。
然而,并非一切都尽如人意。首先,我已经遇到了 DataTables.net 的一个 bug,我已经报告了它,并依赖开源社区来解决它。其次,并非所有都是免费的,Editor 插件是商业化的,单用户许可证/年大约为 119 美元。
对于 C# 使用来说,问题在于 DataTables.AspNet.Core 库 [2], [3] 自 2022 年以来已不再维护。这是我在 ASP.NET/C# 端用于后端处理的开源库。存在一些 bug,您需要自行解决。
3 什么是 DataTables.net
DataTables.net 是一个 jQuery UI 库,在网站 [1] 上有详细描述,因此我在这里不再赘述。仅提及它有两种工作模式:
- 客户端处理
- 服务器端处理
在模式 1) 中,所有表格数据(假设 1000 行)由组件加载到浏览器中,并且所有过滤和排序都在客户端浏览器中使用 JavaScript 完成。从专业使用的角度来看,这是一种比较简单的工作模式。
在模式 2) 中,组件通过 AJAX 调用加载将要显示的数据(假设 15 行),并且在每次用户与组件交互后,都会发出新的 AJAX 调用以获取新数据。在本文中,我们只关注这种工作模式,这是唯一适合专业使用的模式。
3.1 如何获取 DataTables.net
网站 [1] 描述了获取该组件的几种方法,包括 CDN 和 NuGet 包。对我来说,并非所有方法都足够好,所以我发现最适合我的方法是直接从网站下载并直接部署到项目中。
<!-- _LoadingDatatablesJsAndCss.cshtml -->
<link rel="stylesheet" href="~/lib/datatables/datatables.min.css" asp-append-version="true" />
<script src="~/lib/datatables/datatables.min.js" defer asp-append-version="true"></script>
3.2 参考资料
在处理开源项目时,有必要收集所有可用的参考资料。我列出了一个参考列表,以下是其概要:
- DataTables.net 组件在 [1]。有一个很棒的手册描述了 API 和所有选项。
- DataTables.AspNet.Core 库在 [2], [3] 中有描述。这是用于 ASP.NET 后端处理的 C#/.NET 库。在 GitHub 网站上有一些使用它的示例。
- 各种文章和示例 来自 [4]-[11]。如果您发现本教程太难,您可能会发现其中的其他文章更容易上手。
- System.Linq.Dynamic.Core 库在 [12]-[14] 中有描述。这是一个有用的 C# 后端处理库。
4 集成教程
本教程的重点是在 ASP.NET 8、C#、MVC、Bootstrap 5、JavaScript、jQuery 环境中集成各种组件。我不会过多谈论单个组件,它们在其他地方已经有所描述。我也不打算在这里重复所有内容。
4.1 专业人士教程
我不会假装这是一个简单的课题。这是为经验丰富的专业人士准备的教程。我只会指出我前进的方向和额外的参考资料,仅此而已。在这里详细介绍所有内容是在浪费时间和精力。我假设您已经了解 AJAX、Linq、JavaScript、MVC、ASP.NET 等。
5 模拟数据库
我想在我的原型中在 Web UI 的 HTML 表格中展示的是我的 Employee 实体。为了使示例不依赖于任何数据库设置,我创建了一个内存中的模拟数据库,基本上是一个包含 1000 行的 JSON。代码如下:
//Employee.cs =========================================================
namespace Example01.MockDatabase
{
public class Employee
{
public int Id { get; set; } = 0;
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? City { get; set; }
public string? Country { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
}
}
//MockDatabase.cs ======================================================
namespace Example01.MockDatabase
{
public class MockDatabase
{
//table EmployeesTable as an instance
private List<Employee>? _employeeTable;
public List<Employee> EmployeesTable
{
get
{
//going for singleton pattern
if( _employeeTable == null )
{
Employee[]? employeeArray = JsonSerializer.Deserialize<Employee[]>(MockEmployeeData.Data);
if( employeeArray != null )
{
_employeeTable = new List<Employee>();
_employeeTable.AddRange( employeeArray );
}
}
if (_employeeTable == null) { _employeeTable = new List<Employee>(); };
return _employeeTable;
}
}
//database MockDatabase as an instance
private static MockDatabase? _instance;
public static MockDatabase Instance {
get
{
//going for singleton pattern
if (_instance==null)
{
_instance = new MockDatabase();
}
return _instance;
}
}
}
}
//MockEmployeeData.cs =========================================
namespace Example01.MockDatabase
{
public static class MockEmployeeData
{
public static string Data=
@"
[{""Id"":1,""FirstName"":""Saunder"",""LastName"":""Hedger"",""City"":""Poitiers"",""Country"":""France"",""Email"":""shedger0@naver.com"",""Phone"":""170-116-2494""},
{""Id"":2,""FirstName"":""Cornell"",""LastName"":""De la Zenne"",""City"":""San Antonio de los Cobres"",""Country"":""Argentina"",""Email"":""cdelazenne1@narod.ru"",""Phone"":""594-372-0412""},
{""Id"":3,""FirstName"":""Caryn"",""LastName"":""Scarsbrooke"",""City"":""Pelasgía"",""Country"":""Greece"",""Email"":""cscarsbrooke2@sciencedaily.com"",""Phone"":""184-707-0966""},
{""Id"":4,""FirstName"":""Graham"",""LastName"":""Sommerfeld"",""City"":""Nijmegen"",""Country"":""Netherlands"",""Email"":""gsommerfeld3@google.ru"",""Phone"":""927-828-0615""},
{""Id"":5,""FirstName"":""Rosy"",""LastName"":""Sparway"",""City"":""Wiesbaden"",""Country"":""Germany"",""Email"":""rsparway4@goo.gl"",""Phone"":""877-176-1489""},
{""Id"":6,""FirstName"":""Lesli"",""LastName"":""Heaton"",""City"":""Vilhena"",""Country"":""Brazil"",""Email"":""lheaton5@hugedomains.com"",""Phone"":""540-215-7615""},
{""Id"":7,""FirstName"":""Kevon"",""LastName"":""Rumble"",""City"":""Yangxi"",""Country"":""China"",""Email"":""krumble6@usatoday.com"",""Phone"":""805-371-5794""},
{""Id"":8,""FirstName"":""Keefe"",""LastName"":""Ainsley"",""City"":""Cabriz"",""Country"":""Portugal"",""Email"":""kainsley7@jugem.jp"",""Phone"":""140-378-1955""},
{""Id"":9,""FirstName"":""Ninon"",""LastName"":""Baptist"",""City"":""Susapaya"",""Country"":""Peru"",""Email"":""nbaptist8@si.edu"",""Phone"":""111-388-0159""},
{""Id"":10,""FirstName"":""Moira"",""LastName"":""Edwinson"",""City"":""Wangchang"",""Country"":""China"",""Email"":""medwinson9@sphinn.com"",""Phone"":""844-869-9886""},
{""Id"":11,""FirstName"":""Isidoro"",""LastName"":""Meach"",""City"":""Qinglin"",""Country"":""China"",""Email"":""imeacha@huffingtonpost.com"",""Phone"":""951-769-9045""},
{""Id"":12,""FirstName"":""Norry"",""LastName"":""Leggan"",""City"":""Malummaduri"",""Country"":""Nigeria"",""Email"":""nlegganb@altervista.org"",""Phone"":""601-917-3867""},
.....etc...1000 records
6 客户端 DataTables.net 组件
在这里,我将只展示使用 DataTables 组件的 ASP.NET 视图是什么样的。我不会在这里详细介绍所有配置属性,它们在 [1] 的手册中都有很好的记录。我假设您会从 [1] 中学到一些关于组件和配置的知识。我在这里只是展示如何将其与 ASP.NET 集成。我添加了一些注释。我假设您可以阅读 JavaScript/jQuery 代码。
编程风格各异,我选择了我认为最适合我专业项目的风格。我更喜欢明确列出的内容,并非所有人都喜欢这种风格。
请注意 AJAX 的配置部分。该组件通过发出 AJAX 调用来响应用户输入并获取正确的数据。
<!-- Employees.cshtml -->
<partial name="_LoadingDatatablesJsAndCss" />
@{
<div class="text-center">
<h3 class="display-4">Employees table</h3>
</div>
<!-- Here is our table HTML element defined. JavaScript library Datatables
will do all the magic to turn it into interactive component -->
<table id="EmployeesTable01" class="table table-striped table-bordered ">
</table>
}
<script type="text/javascript">
// Datatables script initialization =========================================
// we used defer attribute on jQuery so it might not be available at this point
// so we go for vanilla JS event
document.addEventListener("DOMContentLoaded", InitializeDatatable);
function InitializeDatatable() {
$("#EmployeesTable01").dataTable(
//providing initialization parameters as JavaScript object
{
//processing-Feature control the processing indicator.
processing: true,
//paging-Enable or disable table pagination.
paging: true,
//info-Feature control table information display field
info: true,
//ordering-Feature control ordering (sorting) abilities in DataTables.
ordering: true,
//searching-Feature control search (filtering) abilities
searching: true,
//search.return-Enable / disable DataTables' search on return.
search: {
return: true
},
//autoWidth-Feature control DataTables' smart column width handling.
autoWidth: true,
//lengthMenu-Change the options in the page length select list.
lengthMenu: [10, 15, 25, 50, 100],
//pageLength-Change the initial page length (number of rows per page)
pageLength: 15,
//order-Initial order (sort) to apply to the table.
order: [[1, 'asc']],
//serverSide-Feature control DataTables' server-side processing mode.
serverSide: true,
//Load data for the table's content from an Ajax source.
ajax: {
"url": "@Url.Action("EmployeesDT", "Home")",
"type": "POST",
"datatype": "json"
},
//Set column specific initialisation properties.
columns: [
//name-Set a descriptive name for a column
//data-Set the data source for the column from the rows data object / array
//title-Set the column title
//orderable-Enable or disable ordering on this column
//searchable-Enable or disable search on the data in this column
//type-Set the column type - used for filtering and sorting string processing
//visible-Enable or disable the display of this column.
//width-Column width assignment.
//render-Render (process) the data for use in the table.
//className-Class to assign to each cell in the column.
{ //0
name: 'id',
data: 'id',
title: "Employee Id",
orderable: true,
searchable: false,
type: 'num',
visible: false
},
{
//1
name: 'givenName',
data: "givenName",
title: "Given Name",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
//2
name: 'familyName',
data: "familyName",
title: "Family Name",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
//3
name: 'town',
data: "town",
title: "Town",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
//4
name: 'country',
data: "country",
title: "Country",
orderable: true,
searchable: true,
type: 'string',
visible: true,
width: "150px",
className: 'text-center '
},
{
//5
name: 'email',
data: "email",
title: "Email",
orderable: true,
searchable: true,
type: 'string',
visible: true
},
{
//6
name: 'phoneNo',
data: "phoneNo",
title: "Phone Number",
orderable: false,
searchable: true,
type: 'string',
visible: true
}
]
} // end of initialization object
);
}
</script>
7 示例 AJAX 调用
为了更好地理解正在发生的事情,我使用了 Chrome DevTools 来捕获示例 AJAX 调用中的出站和入站数据。
这是 请求
draw: 2 columns[0][data]: id columns[0][name]: id columns[0][searchable]: false columns[0][orderable]: true columns[0][search][value]: columns[0][search][regex]: false columns[1][data]: givenName columns[1][name]: givenName columns[1][searchable]: true columns[1][orderable]: true columns[1][search][value]: columns[1][search][regex]: false columns[2][data]: familyName columns[2][name]: familyName columns[2][searchable]: true columns[2][orderable]: true columns[2][search][value]: columns[2][search][regex]: false columns[3][data]: town columns[3][name]: town columns[3][searchable]: true columns[3][orderable]: true columns[3][search][value]: columns[3][search][regex]: false columns[4][data]: country columns[4][name]: country columns[4][searchable]: true columns[4][orderable]: true columns[4][search][value]: columns[4][search][regex]: false columns[5][data]: email columns[5][name]: email columns[5][searchable]: true columns[5][orderable]: true columns[5][search][value]: columns[5][search][regex]: false columns[6][data]: phoneNo columns[6][name]: phoneNo columns[6][searchable]: true columns[6][orderable]: false columns[6][search][value]: columns[6][search][regex]: false order[0][column]: 1 order[0][dir]: asc order[0][name]: givenName start: 0 length: 15 search[value]: mark search[regex]: false
这是 响应
{ "draw": 2, "recordsTotal": 1000, "recordsFiltered": 7, "data": [ { "id": 867, "givenName": "Ambur", "familyName": "Key", "town": "San Pedro de Macorís", "country": "Dominican Republic", "email": "akeyo2@domainmarket.com", "phoneNo": "196-987-6349" }, { "id": 224, "givenName": "Artus", "familyName": "Ledekker", "town": "Renxian", "country": "China", "email": "aledekker67@domainmarket.com", "phoneNo": "480-208-2782" }, { "id": 60, "givenName": "Eolanda", "familyName": "Briamo", "town": "Francisco I Madero", "country": "Mexico", "email": "ebriamo1n@domainmarket.com", "phoneNo": "467-917-3423" }, { "id": 15, "givenName": "Ermanno", "familyName": "Sirett", "town": "København", "country": "Denmark", "email": "esirette@about.com", "phoneNo": "249-103-3769" }, { "id": 822, "givenName": "Jany", "familyName": "Cleen", "town": "Shādegān", "country": "Iran", "email": "jcleenmt@marketwatch.com", "phoneNo": "605-915-2463" }, { "id": 416, "givenName": "Sunshine", "familyName": "Sandbrook", "town": "Shigu", "country": "China", "email": "ssandbrookbj@marketwatch.com", "phoneNo": "479-324-0262" }, { "id": 406, "givenName": "Teresina", "familyName": "Sreenan", "town": "Staryy Saltiv", "country": "Ukraine", "email": "tsreenanb9@domainmarket.com", "phoneNo": "278-996-2943" } ] }
这是生成的表格
我当然只提取了相关的数据部分。 每一个优秀的 Web 程序员都应该能够从上面的数据中理解发生了什么。
8 DataTables.AspNet.Core 库
如果您是 ASP.NET 后端程序员,那么来自上述示例 AJAX 调用的问题是如何在服务器端解析所有这些输入数据。
答案是,我正在使用 DataTables.AspNet.Core 库 [2], [3]。该库既是解决方案也是问题。它免费提供了有用的实用工具,但自 2022 年以来已不再维护。我的想法是,如果我不使用该库,我将需要编写自己的版本,可能最终会得出非常相似的设计解决方案。因此,我决定使用它并在需要时进行修补。
如何学习这个库?没有教程可用。因此,我只是从 GitHub 下载了源代码,并从源代码中学习。在同一个网站上也有一些作为演示的示例。
从现在开始,我将在本文的代码中仅使用该库。
9 System.Linq.Dynamic.Core 库
还有一个库对本应用程序的后端处理很有用,那就是 System.Linq.Dynamic.Core 库 [12]-[14]。[14] 提供了一个很棒的教程。从现在开始,我将在本文的代码中仅使用该库。
10 ASP.NET 后端处理
现在我们进入 C#/.NET 部分,编写我们的 ASP.NET 代码。这是我提出的解决方案。我不会假装它很简单,它假设您对 DataTables.AspNet.Core 和 System.Linq.Dynamic.Core 库有很好的理解。我利用这些库和 LINQ 来解决问题。
我追求的是通用解决方案和可重用代码。否则,对于每个表格,我都需要为每个新实体重新编写专门的过滤和排序方法。
这是代码
//HomeController.cs ======================================
//this is target of AJAX call and provides data for
//the table, based on selected input parameters
public IActionResult EmployeesDT(DataTables.AspNet.Core.IDataTablesRequest request)
{
// There is dependency in this method on names of fields
// and implied mapping. I see it almost impossible to avoid.
// At least, in this method, we avoided dependency on the order
// of table fields, in case order needs to be changed
//Here are our mapped table columns:
//Column0 id -> Employee.Id
//Column1 givenName -> Employee.FirstName
//Column2 familyName -> Employee.LastName
//Column3 town -> Employee.City
//Column4 country -> Employee.Country
//Column5 email -> Employee.Email
//Column6 phoneNo -> Employee.Phone
try
{
IQueryable<Employee> employees = MockDatabase.MockDatabase.Instance.EmployeesTable.AsQueryable();
//here we get the count that needs to be presented by the UI
int totalRecordsCount = employees.Count();
var iQueryableOfAnonymous = employees.Select(p => new
{
id = p.Id,
givenName = p.FirstName,
familyName = p.LastName,
town = p.City,
country = p.Country,
email = p.Email,
phoneNo = p.Phone,
});
iQueryableOfAnonymous = FilterRowsPerRequestParameters(iQueryableOfAnonymous, request);
//here we get the count that needs to be presented by the UI
int filteredRecordsCount = iQueryableOfAnonymous.Count();
iQueryableOfAnonymous = SortRowsPerRequestParamters(iQueryableOfAnonymous, request);
iQueryableOfAnonymous = iQueryableOfAnonymous.Skip(request.Start).Take(request.Length);
//here we materialize the query
var dataPage = iQueryableOfAnonymous.ToList();
var response = DataTablesResponse.Create(request, totalRecordsCount, filteredRecordsCount, dataPage);
return new DataTablesJsonResult(response, false);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
var response = DataTablesResponse.Create(request, "Error processing AJAX call on server side");
return new DataTablesJsonResult(response, false);
}
}
private IQueryable<T> SortRowsPerRequestParamters<T>(
IQueryable<T> iQueryableOfAnonymous, DataTables.AspNet.Core.IDataTablesRequest request)
{
/*
* So, in "IQueryable<T> iQueryableOfAnonymous" I have the source data that I need to
* sort. In "DataTables.AspNet.Core.IDataTablesRequest request" I have full specification
* of sorting required, including the specification of columns
* that need to be sorted by marked with "IsSortable" and "Sort.Order".
* I use Reflection to check if names of entity T properties match column names in
* DataTables.AspNet.Core.IDataTablesRequest request and then create sort Linq query using
* System.Linq.Dynamic.Core library, and return Linq query as a result.
* It might look complicated, but advantage is that this method is generic and can
* be applied many times on different entities T.
*/
if (request != null && request.Columns != null && request.Columns.Any())
{
//this will work if type T contains properties that have names column.Name
var sortingColumns = request.Columns.Where(p => p.IsSortable && p.Sort != null).OrderBy(p => p.Sort.Order).ToList();
Type objType = typeof(T);
var ListOfAllPropertiesInT = objType.GetProperties().Select(p => p.Name).ToList();
if (sortingColumns != null && sortingColumns.Count > 0)
{
//we plan to build dynamic Linq expression in this string
string dynamicLinqOrder = string.Empty;
bool isFirstString = true;
for (int i = 0; i < sortingColumns.Count; i++)
{
var column = sortingColumns[i];
//check if that property exists in T,
//otherwise we will create syntax error in dynamic linq
if (ListOfAllPropertiesInT.Contains(column.Name))
{
if (isFirstString)
{
isFirstString = false;
}
else
{
dynamicLinqOrder += ", ";
}
dynamicLinqOrder += column.Name;
if (column.Sort.Direction == SortDirection.Descending)
{
dynamicLinqOrder += " desc";
}
}
}
if (dynamicLinqOrder.Length > 0)
{
//using System.Linq.Dynamic.Core
iQueryableOfAnonymous = iQueryableOfAnonymous.OrderBy(dynamicLinqOrder);
}
};
}
return iQueryableOfAnonymous;
}
private IQueryable<T> FilterRowsPerRequestParameters<T>(
IQueryable<T> iQueryableOfAnonymous, DataTables.AspNet.Core.IDataTablesRequest request)
{
/*
* So, in "IQueryable<T> iQueryableOfAnonymous" I have the source data that I need to
* filter. In "DataTables.AspNet.Core.IDataTablesRequest request" I have full specification
* of request, including the search value "request.Search.Value" and specification of columns
* that need to be searched for marked by "IsSearchable".
* I use Reflection to check if names of entity T properties match column names in
* DataTables.AspNet.Core.IDataTablesRequest request and then create filter Linq query using
* System.Linq.Dynamic.Core library, and return Linq query as a result.
* It might look complicated, but advantage is that this method is generic and can
* be applied many times on different entities T.
*/
//this will work if type T contains properties that have names column.Name
if (request != null && request.Search != null && !System.String.IsNullOrEmpty(request.Search.Value))
{
string pattern = request.Search.Value?.Trim() ?? System.String.Empty;
var searchingColumns = request.Columns.Where(p => p.IsSearchable).ToList();
var config = new ParsingConfig { ResolveTypesBySimpleName = true };
Type objType = typeof(T);
var ListOfAllPropertiesInT = objType.GetProperties().Select(p => p.Name).ToList();
if (searchingColumns.Count > 0)
{
//we plan to build dynamic Linq expression in this string
string dynamicLinqSearch = string.Empty;
bool isFirstString = true;
for (int i = 0; i < searchingColumns.Count; i++)
{
var column = searchingColumns[i];
//check if that property exists in T,
//otherwise we will create syntax error in dynamic linq
if (ListOfAllPropertiesInT.Contains(column.Name))
{
if (isFirstString)
{
isFirstString = false;
}
else
{
dynamicLinqSearch += " or ";
}
dynamicLinqSearch += $"""{column.Name}.Contains("{pattern}")""";
}
}
if (dynamicLinqSearch.Length > 0)
{
//using System.Linq.Dynamic.Core
iQueryableOfAnonymous = iQueryableOfAnonymous.Where(config, dynamicLinqSearch);
}
}
}
return iQueryableOfAnonymous;
}
11 结论
DataTables.net 组件看起来不错,并且在我的原型中运行得相当好。学习的成本不低,但如果组件在多个项目中频繁使用,它就能物有所值。如果您是 ASP.NET 开发人员,拥有这样一个组件作为工具集是很有益的。
开源代码的问题在于,您需要依赖社区来维护您集成到代码中的组件/库的最新版本。bug 总是会发生,这可能会成为一个问题。在此特定情况下,DataTables.AspNet.Core 库已不再维护,因此如果 DataTables.net 或 ASP.NET 的接口发生变化,您将需要自行修复问题。
可以下载完整的示例代码项目。
12 参考资料
[1] https://datatables.net.cn/
[2] https://nuget.net.cn/packages/DataTables.AspNet.Core/
DataTables.AspNet.Core
[3] https://github.com/ALMMa/datatables.aspnet
[4] https://www.c-sharpcorner.com/article/using-datatables-grid-with-asp-net-mvc/
在 ASP.NET MVC 中使用 DataTables 网格(MVC5)
[5] https://www.c-sharpcorner.com/article/display-loading-or-processing-message-inside-datatable/
在 DataTable 中显示加载或处理消息
[6] https://www.c-sharpcorner.com/article/create-datatable-in-jquery/
用 JQuery 创建一个 datatable(纯 jQuery)
[7] https://www.c-sharpcorner.com/article/server-side-rendering-of-datatables-js-in-asp-net-core/
在 ASP.NET Core 中服务器端渲染 DataTables JS (2024)
[8] https://www.c-sharpcorner.com/article/effortless-pagination-with-jquery-datatables-and-bootstrap/
使用 jQuery DataTables 和 Bootstrap 实现轻松分页(非常基础)
[9] https://www.c-sharpcorner.com/article/asp-net-mvc-jquery-server-side-datatable-example/
ASP.NET MVC jQuery 服务器端 Datatable 示例
[10] https://www.c-sharpcorner.com/article/pagination-in-mvc-with-jquery-datatable/
在 MVC 中使用 Jquery DataTable 实现分页
[11] https://codewithmukesh.com/blog/jquery-datatable-in-aspnet-core/#google_vignette
ASP.NET Core 中的 JQuery Datatable – 服务器端处理
[12] https://nuget.net.cn/packages/System.Linq.Dynamic.Core
System.Linq.Dynamic.Core
[13] https://github.com/zzzprojects/System.Linq.Dynamic.Core
zzzprojects/ System.Linq.Dynamic.Core
[14] https://dynamic-linq.net/
一个免费且开源的 LINQ 动态查询库
[21] ASP.NET8 使用 DataTables.net – 第1部分 – 基础
https://codeproject.org.cn/Articles/5385033/ASP-NET-8-Using-DataTables-net-Part1-Foundation
[22] ASP.NET8 使用 DataTables.net – 第2部分 – 操作按钮
https://codeproject.org.cn/Articles/5385098/ASP-NET8-using-DataTables-net-Part2-Action-buttons
[23] ASP.NET8 使用 DataTables.net – 第3部分 – 状态保存
https://codeproject.org.cn/Articles/5385308/ASP-NET8-using-DataTables-net-Part3-State-saving
[24] ASP.NET8 使用 DataTables.net – 第4部分 – 多语言
https://codeproject.org.cn/Articles/5385407/ASP-NET8-using-DataTables-net-Part4-Multilingual
[25] ASP.NET8 使用 DataTables.net – 第5部分 – 在 AJAX 中传递附加参数
https://codeproject.org.cn/Articles/5385575/ASP-NET8-using-DataTables-net-Part5-Passing-additi
[26] ASP.NET8 使用 DataTables.net – 第6部分 – 在 AJAX 中返回附加参数
https://codeproject.org.cn/Articles/5385692/ASP-NET8-using-DataTables-net-Part6-Returning-addi
[27] ASP.NET8 使用 DataTables.net – 第7部分 – 常规按钮
https://codeproject.org.cn/Articles/5385828/ASP-NET8-using-DataTables-net-Part7-Buttons-regula
[28] ASP.NET8 使用 DataTables.net – 第8部分 – 选择行
https://codeproject.org.cn/Articles/5386103/ASP-NET8-using-DataTables-net-Part8-Select-rows
[29] ASP.NET8 使用 DataTables.net – 第9部分 – 高级筛选器
https://codeproject.org.cn/Articles/5386263/ASP-NET8-using-DataTables-net-Part9-Advanced-Filte