65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET Core 数据网格

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2017 年 1 月 18 日

CPOL

7分钟阅读

viewsIcon

74554

downloadIcon

3081

.Net Core 数据网格,支持服务器端分页、排序和过滤

在许多 Web 应用程序中,以表格格式显示数据是一项重要任务。本博客将演示如何使用 Bootstrap Table 插件来显示数据。该演示展示了服务器端分页、过滤和排序等高级功能。

通过演示应用程序,我将更详细地介绍以下方面:

  1. 设置 Bootstrap Table 插件
  2. 设置 DataSource
  3. cshtml 文件中的表格定义
  4. 自定义单元格渲染
  5. 服务器端分页、排序和过滤
  6. 高亮显示选定行
  7. 自定义工具栏
  8. 附加 Load 参数

Bootstrap Table 插件

市面上有许多数据网格包,它们各有所长。在本演示中,我使用了 Bootstrap Table 插件。它是一个免费插件,功能丰富。

  1. Ajax 支持
  2. 服务器端分页、过滤和排序
  3. 易于使用
  4. 轻量级且快速
  5. Bootstrap 支持
  6. 提供第三方插件以扩展功能

设置 Bootstrap Table 插件

启动 Visual Studio,创建一个新的 .NET Core 项目,不包含身份验证。

Bootstrap Table 是一个 JavaScript 库,您只需将其库文件包含在应用程序中即可。它包含两个文件:一个 JavaScript 文件和一个 CSS 文件。`rendersection` 设置在 `_Layout.cshtml` 文件的末尾。`rendersection` 为单个 cshtml 页面提供了一个钩子。此钩子在渲染单个 cshtml 页面时执行。

  ...
  @RenderSection("scripts", required: false)
</body>
</html>
...
@section scripts { 
  @await Html.PartialAsync("bootstraptable")
 
  <script type="text/javascript">
  ...

此链式调用渲染位于共享视图文件夹中的共享 `bootstraptable.cshtml` 文件。这使得在其他页面上易于重用,并且文件被及时加载以获得最佳性能。Dot Net Core 提供了一个简洁的解决方案来区分生产环境和开发环境的文件。在开发环境中,您可以使用较大、易于阅读的文件;而在生产环境中,您会自动切换到更小、更快的最小化文件。

<environment names="Development">
  <link rel="stylesheet" href="~/css/bootstrap-table.css">
  <script src="~/lib/bootstrap-table/bootstrap-table.js"></script>
</environment>
<environment names="Staging,Production">
  <link rel="stylesheet" 
   href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.11.0/bootstrap-table.min.js">
  </script>
</environment>

设置 DataSource

在大多数情况下,数据都来自数据库服务器或 REST API。本演示侧重于如何显示数据,而非如何获取数据。在此示例中,数据从位于 Web 服务器上的一个简单的 JSON 文件中获取。这使得数据源保持简单且易于设置。控制器有一个 `private IList countries` 属性。`Country` 是一个简单的 POCO 类。

  public class Country
  {
    [Key()]
    public Int32 Code { get; set; }    
    
    [StringLength(2, MinimumLength =2)]
    [Display(Name="2 Digit ISO")]
    public String ISO2{ get; set; }

    [StringLength(3, MinimumLength = 3)]
    [Display(Name = "3 Digit ISO")]
    public String ISO3 { get; set; }

    public String Name { get; set; }
  }

Entity Framework 支持

此示例使用 `IList` 作为数据源,但它可以轻松地被 Entity Framework 提供的 `DbSet` 替换。只要数据源可以转换为非泛型的 IQueryable 类型,该演示就适用于任何类型的数据源。

cshtml 文件中的表格定义

您可以在 HTML 或 jQuery 中设置表格定义。我个人更喜欢 HTML,因为它更容易阅读。也可以将两者结合使用。在本演示中,HTML 用于设置表格布局,而表格事件则在 jQuery 中处理。`

` 部分的 `Data` 属性用于设置表格行为,而列和行则在 `` 部分配置。

<table id="table"
         data-unique-id="Code"
         data-sort-name="Code"
         data-sort-order="asc"
         data-classes="table table-condensed table-hover table-striped"
         data-toggle="table"
         data-side-pagination="server"
         data-url="Load"
         data-pagination="true"
         data-search="true"
         data-show-refresh="true"
         data-toolbar="#toolbar"
         data-page-size="20"
         data-page-list="[5,10,20,50,100,All]">
    <thead>
      <tr>
        <th data-field="ISO2" data-sortable="false" 
        data-halign="center" data-align="center" 
         data-formatter="flagFormatter">Flag</th>
        <th data-field="Code" data-sortable="true" 
        data-halign="center" data-align="center">Code</th>
        <th data-field="ISO3" data-sortable="true">ISO 3</th>
        <th data-field="Name" data-sortable="true">Name</th>
      </tr>
    </thead>
  </table>

可以通过属性配置列设置,例如对齐方式、宽度、日期和数字格式。`data-unique-id="Code"` 设置主键列。在渲染过程中,每一行表格都会获得一个 `data-uniqueid` 属性,其中包含其键值。

<tr data-uniqueid="4" data-index="0">
  ...
</tr>

此键属性对于 CRUD(创建、检索、更新和删除)操作至关重要。使用 jQuery,您可以轻松地从行中检索键值。如果 `data-unique-id` 为空或未设置,表格仍会渲染。`data-sort-name` 和 `data-sort-order` 属性用于设置初始排序列和排序顺序。这些值在数据请求期间会传递给控制器。

自定义单元格渲染

Bootstrap Table 通过 `data-formatter` 属性支持自定义单元格渲染。如果标准配置选项不够用,这提供了最大的灵活性。

 function flagFormatter(value, row) {
      return '<img src="/images/flags/' + value.toLowerCase() + '.png" >';
    }

`value` 参数是列值,`row` 参数包含所有行值。

服务器端分页、排序和过滤

只需几个属性即可设置服务器端分页、排序和过滤。

data-side-pagination="server"
data-url="Load"
data-pagination="true"
data-search="true"

`data-url="Load"` 属性指定控制器类有一个 `public Load` 函数。`Load` 函数处理输入参数并返回一个 JSON 文档。

[HttpGet]
public virtual ActionResult Load(String sort, String order, String search, Int32 limit, Int32 offset)
{
   // Get entity fieldnames
   List<String> columnNames = typeof(Country).GetProperties(BindingFlags.Public | 
                              BindingFlags.Instance).Select(p => p.Name).ToList();

   // Create a separate list for searchable field names   
   List<String> searchFields = new List<String>(columnNames);

   // Exclude field Iso2 for filtering 
   searchFields.Remove("ISO2");

   // Perform filtering
   IQueryable items = SearchItems(countries.AsQueryable(), search, searchFields);

   // Sort the filtered items and apply paging
   return Content(ItemsToJson
   (items, columnNames, sort, order, limit, offset), "application/json");
}

输入参数

  • `sort`: 排序列名
  • `order`: 排序方向,asc 或 desc
  • `search`: 用户输入的搜索参数
  • `limit`: 每页记录数
  • `offset`: 在获取数据页之前要跳过的记录数

JSON 输出

  • `total`: 过滤后可用的记录数
  • `rows`: 包含国家记录的数组

数组的容量等于或小于 `limit` 页大小。

  "total": 193,
  "rows": [
    {
      "Code": 4,
      "ISO2": "AF",
      "ISO3": "AFG",
      "Name": "Afghanistan"
    },
    {
      "Code": 8,
      "ISO2": "AL",
      "ISO3": "ALB",
      "Name": "Albania"
    },
   ...

排除过滤字段

ISO2 字段用于渲染国旗图像,代码本身对用户不可见。在此 GUI 设计中,搜索参数仅适用于可见数据。这意味着 `ISO2` 属性必须从可搜索字段中排除。

// Get entity fieldnames
List<String> columnNames = typeof(Country).GetProperties(BindingFlags.Public | 
                           BindingFlags.Instance).Select(p => p.Name).ToList();

// Create a separate list for searchable field names   
List<String> searchFields = new List<String>(columnNames);

// Exclude field Iso2 for filtering 
searchFields.Remove("ISO2");

// Perform filtering
IQueryable items = SearchItems(countries.AsQueryable(), search, searchFields);

使用 Dynamic Linq 进行可重用过滤

Linq 是一项了不起的创新。它提供了对可枚举集合执行查询的能力。与 SQL 不同,Linq 具有编译时语法检查。这在大多数情况下都很有帮助。它在编译时而不是运行时检测错误。如果我想创建一个可重用的过滤方法,编译时语法会成为障碍。不同的实体具有不同的字段名,那么如何将不同的字段名传递给一个可重用的方法?运行时解析而不是编译时解析是解决方案。Dynamic Linq Core 包正是做到了这一点。它能与 Linq 无缝集成,并为 `where` 子句、排序和其他操作提供额外的重载函数。Dynamic Linq 用于 `SearchItems` 以在运行时创建 `searchExpression`。

protected virtual IQueryable SearchItems(IQueryable items, String search, List<String> columnNames)
{
  // Apply filtering to all visible column names
  if (search != null && search.Length > 0)
  {
    StringBuilder sb = new StringBuilder();

    // create dynamic Linq expression
    foreach (String fieldName in columnNames)
      sb.AppendFormat("({0} == null ? 
      false : {0}.ToString().IndexOf(@0, @1) >=0) or {1}", 
                                      fieldName, Environment.NewLine);

    String searchExpression = sb.ToString();
    // remove last "or" occurrence
    searchExpression = searchExpression.Substring
    (0, searchExpression.LastIndexOf("or"));

    // Apply filtering, 
    items = items.Where
    (searchExpression, search, StringComparison.OrdinalIgnoreCase);
  }
  return items;
}

由 `SearchItems` 生成的 `Country searchExpression`

(Code == null ? false : Code.ToString().IndexOf(@0, @1) >=0) or 
(ISO3 == null ? false : ISO3.ToString().IndexOf(@0, @1) >=0) or 
(Name == null ? false : Name.ToString().IndexOf(@0, @1) >=0)

请注意,`ISO2` 字段不在 `searchExpression` 中,这符合预期。在本演示中,`SearchItems` 的实现相当直接。如果需要更复杂的过滤,可以覆盖 `SearchItems` 以满足新需求。

生成 JSON 文档

`ItemsToJson` 函数创建 Bootstrap Table 所使用的 JSON 文档。

protected String ItemsToJson(IQueryable items, List<String> columnNames, 
                             String sort, String order, Int32 limit, Int32 offset)
{
  try
  {
	// where clause is set, count total records
	Int32 count = items.Count();

	// Skip requires sorting, so make sure there is always sorting
	String sortExpression = "";
   
	if (sort != null && sort.Length > 0)
	  sortExpression += String.Format("{0} {1}", sort, order);

	// show all records if limit is not set
	if (limit == 0)
	  limit = count;

	// Prepare json structure
	var result = new
	{
	  total = count,
	  rows = items.OrderBy(sortExpression).Skip(offset).Take(limit).Select
             ("new (" + String.Join(",", columnNames) + ")")
	};

	return JsonConvert.SerializeObject(result, Formatting.None, new JsonSerializerSettings() 
                  { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
  }
  catch (Exception ex)
  {
	Console.WriteLine(ex.Message);
	return null;
  }
}

输入参数

  • `items`: 未排序、已过滤的实体集
  • `columnNames`: 包含在 JSON 文档中的字段
  • `sort`: 排序列名
  • `order`: 排序方向,asc 或 desc
  • `search`: 用户输入的搜索参数
  • `limit`: 每页记录数
  • `offset`: 在获取数据页之前要跳过的记录数

`columnNames` 变量限制了在 JSON 文档中公开的属性数量。如果您不想出于性能或安全原因显示所有可用的实体属性,这会很有用。分页需要排序,而排序由 Dynamic Linq 提供。分页是通过标准的 Linq `Skip` 和 `Take` 函数实现的。`Formatting.None` 选项会减小 JSON 文档的大小,提高性能,但使其更难阅读。我只在调试时使用 `Formatting.Indented` 选项。

高亮显示选定行

Bootstrap Table 具有多种行选择选项。它甚至可以记住上一页中选定的行,我认为这非常酷。您可以阅读文档了解如何实现这一点。在本演示应用程序中,我使用了 jQuery 和 CSS。在论坛上,关于这个话题有很多问题,所以让我们在这里多加关注。首先,我修改了 CSS 样式,使选定行更加醒目。我可以覆盖 Bootstrap 的 CSS 文件,但在更新时所有工作都会丢失。在 `site.css` 文件中设置新样式可以避免这种风险。

/* selected row style */
.table-striped tbody .highlight td {
  background-color: #b5b5b5;
}

下一步是将 Bootstrap Table 的行点击事件附加到 `highLightRow` 函数。

// register row-click event
$('#table').on('click-row.bs.table', function ($element, row, $tr) {
  highLightRow($tr);
});

`highLightRow` 函数将高亮 CSS 类应用于选定的行,并从所有其他行中移除 CSS 类。这确保了一次只有一个行被选中。

function highLightRow($tr) {
   $tr.addClass('highlight').siblings().removeClass('highlight');
}

自定义工具栏

使用 Bootstrap Table,您可以使用纯 HTML 自定义工具栏。在某些其他包中,您需要了解很多关于网格的细节及其 API。Bootstrap Table 非常简单。在具有 `id` 的 `div` 中创建您的工具栏按钮和其他控件。将此 `id` 分配给 `data-toolbar` 属性,即可完成!

<div id="toolbar">
  <button id="btninfo" title="Show selected row info" class="btn btn-default" type="button">
  <i class="glyphicon glyphicon-info-sign"></i>&nbsp;row info</button>
</div>>
<table id="table"
 ...
 data-toolbar="#toolbar"
 ...

附加 Load 参数

有时,GUI 要求将附加参数发送到控制器。这只需要几个简单的步骤。首先,使用 `data-query-params` 属性设置注入额外参数的函数。

<table id="table"
 ...
 data-query-params="extraServerParams"
 ...

在本演示中,附加参数是固定的,通常您会使用输入控件的值。

function extraServerParams(params) {
   params.ExtraParam = 2;
   return params;
}

最后一步是修改控制器上的 Load 函数以处理附加参数。

结论

在许多应用程序中,在网格中显示数据是一项重要需求。Bootstrap Table 在此方面做得非常出色。它易于设置和使用,并且与 Bootstrap 和 Dot Net Core 配合良好。Dynamic Linq 使解决方案具有高度的可重用性。我添加了演示应用程序,以便您可以进行尝试。如果您有任何意见或问题,请告诉我。

延伸阅读

.NET Core Datagrid - CodeProject - 代码之家
© . All rights reserved.