.net 10 blazor server QuickGrid添加,更新,删除,过滤,排序分页
Table of Contents
Data sources
QuickGrid supports the following data sources
In-memory IQueryable
EF-Core IQueryable
Remote dataDataProvider - Items
<QuickGrid Items="Stocks">
全部交给 QuickGrid
↓
QuickGrid 自己处理
DataProvider - ItemsProvider
<QuickGrid ItemsProvider="LoadStocks">
QuickGrid
↓
“我现在需要第 100~119 条”
↓
LoadStocks()
↓
返回 20 条
从服务器上基于 JSON 的 Web API 获取数据是一项常见要求。
要提取当前页/数据视区所需的数据,并在服务器上应用排序或筛选规则,使用 ItemsProvider 参数。
提供与 GridItemsProvider<TGridItem> 委托类型匹配的回调,其中 TGridItem 是网格中显示的数据类型。 该回调被赋予一个 GridItemsProviderRequest<TGridItem> 类型的参数,它指定了要返回的数据的起始索引、最大行计数和排序顺序。 除了返回匹配项之外,分页和虚拟化还需要总项计数 (totalItemCount) 才能正常运行。
Sorting and remote data (ItemsProvider)
QuickGrid 搭配 ItemsProvider 时属于服务端排序/分页模式。需要利用 request.SortByColumn 获取用户点击的列,并将排序依据传递给数据库 SQL(ORDER BY),前端网格无法对后台仅返回的单页数据进行正确内存排序。
Virtualize
Virtualize="true":几千~几万条 JSON 股票数据,QuickGrid 不一次渲染全部行,而是滚动时上下滚动,动态加载/渲染。
Pagination:第 1 页、第 2 页、第 3 页
每个页面必须加上 @rendermode InteractiveServer
所有页面都支持 Interactive
在 Components/Routes.razor 中统一指定。
<Routes @rendermode="InteractiveServer" />
QuickGrid-ItemsProvider-Paginator.razor
<h3>QuickGrid_ItemsProvider_Paginator</h3>
@page "/StockDataPhysicalDisk/QuickGrid_ItemsProvider_Paginator"
@using Microsoft.AspNetCore.Components.QuickGrid
@using System.Timers
@rendermode InteractiveServer
@implements IDisposable
<PageTitle>QuickGrid 分页示例</PageTitle>
<h3>员工名册 (分页模式)</h3>
<!-- 1. 查找功能区 -->
<div class="search-container mb-3 d-flex align-items-center gap-3">
<div>
<label for="searchBox">查找: </label>
<input id="searchBox"
type="text"
class="form-control d-inline-block w-auto"
placeholder="按姓名或职位搜索..."
@bind="SearchTerm"
@bind:event="oninput" />
</div>
<div>
<label for="pageSize">每页显示: </label>
<select id="pageSize" class="form-select d-inline-block w-auto" @bind="pagination.ItemsPerPage">
<option value="5">5 条</option>
<option value="10">10 条</option>
<option value="20">20 条</option>
<option value="50">50 条</option>
</select>
</div>
</div>
<!-- 2. 数据表格区 (绑定了 Pagination) -->
<div class="grid-container border rounded">
<QuickGrid TGridItem="Employee"
ItemsProvider="@employeeProvider"
Pagination="@pagination"
@ref="grid">
<PropertyColumn Property="@(e => e.Id)" Title="编号" Sortable="true" />
<PropertyColumn Property="@(e => e.Name)" Title="姓名" Sortable="true" />
<PropertyColumn Property="@(e => e.Position)" Title="职位" Sortable="true" />
<PropertyColumn Property="@(e => e.Salary)" Title="薪水" Format="C0" Sortable="true" />
<PropertyColumn Property="@(e => e.JoinDate)" Title="入职日期" Format="yyyy-MM-dd" Sortable="true" />
</QuickGrid>
</div>
<!-- 3. 分页控件区 -->
<div class="mt-3">
@* Paginator 是 QuickGrid 提供的配套分页UI组件 *@
<Paginator State="@pagination" />
</div>
@code {
// 组件引用和状态
private QuickGrid<Employee>? grid;
private GridItemsProvider<Employee>? employeeProvider;
private IQueryable<Employee> fakeDatabase = default!;
// 分页状态管理 (默认每页 10 条)
private PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
// 搜索防抖定时器
private Timer? debounceTimer;
private string _searchTerm = string.Empty;
private string SearchTerm
{
get => _searchTerm;
set
{
_searchTerm = value;
debounceTimer?.Stop();
debounceTimer?.Start();
}
}
protected override void OnInitialized()
{
// 模拟生成 1,000 条测试数据作为数据库
var list = new List<Employee>();
var positions = new[] { "前端开发", "后端开发", "产品经理", "测试工程师", "UI设计师", "运维工程师" };
var random = new Random();
for (int i = 1; i <= 1000; i++)
{
list.Add(new Employee
{
Id = i,
Name = $"员工_{i:D4}",
Position = positions[random.Next(positions.Length)],
Salary = random.Next(8000, 40000),
JoinDate = DateTime.Now.AddDays(-random.Next(1, 3000))
});
}
fakeDatabase = list.AsQueryable();
// 订阅分页变化事件:当用户改变“每页条数”时,可能需要回到第一页
pagination.TotalItemCountChanged += (sender, args) => StateHasChanged();
// 初始化防抖定时器 (300毫秒)
debounceTimer = new Timer(300);
debounceTimer.AutoReset = false;
debounceTimer.Elapsed += async (sender, args) =>
{
await InvokeAsync(async () =>
{
// 搜索条件改变时,应当将页码重置为第一页
await pagination.SetCurrentPageIndexAsync(0);
if (grid != null)
{
await grid.RefreshDataAsync();
}
});
};
// 核心:配置 ItemsProvider 委托
employeeProvider = async request =>
{
var query = fakeDatabase;
// A. 执行过滤 (查找)
if (!string.IsNullOrWhiteSpace(_searchTerm))
{
query = query.Where(e => e.Name.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase) ||
e.Position.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase));
}
// B. 执行排序
if (request.SortByColumn != null)
{
var sortTitle = request.SortByColumn.Title;
var isAsc = request.SortByAscending;
query = sortTitle switch
{
"编号" => isAsc ? query.OrderBy(e => e.Id) : query.OrderByDescending(e => e.Id),
"姓名" => isAsc ? query.OrderBy(e => e.Name) : query.OrderByDescending(e => e.Name),
"职位" => isAsc ? query.OrderBy(e => e.Position) : query.OrderByDescending(e => e.Position),
"薪水" => isAsc ? query.OrderBy(e => e.Salary) : query.OrderByDescending(e => e.Salary),
"入职日期" => isAsc ? query.OrderBy(e => e.JoinDate) : query.OrderByDescending(e => e.JoinDate),
_ => query
};
}
else
{
query = query.OrderBy(e => e.Id);
}
// C. 必须计算总数 (Paginator 需要靠这个计算总共有多少页)
// 在真实数据库中请使用 Entity Framework 的 await query.CountAsync()
var totalCount = query.Count();
// D. 获取当前页的数据切片 (Skip 和 Take)
// 当 QuickGrid 绑定了 PaginationState 时,request.StartIndex 和 request.Count 会由组件自动计算
var pagedData = query.Skip(request.StartIndex)
.Take(request.Count ?? pagination.ItemsPerPage)
.ToList(); // 真实数据库请使用 await query.ToListAsync()
// 模拟数据库查询耗时
await Task.Delay(100);
return GridItemsProviderResult.From(items: pagedData, totalItemCount: totalCount);
};
}
public void Dispose()
{
debounceTimer?.Dispose();
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Position { get; set; } = string.Empty;
public decimal Salary { get; set; }
public DateTime JoinDate { get; set; }
}
}
QuickGrid_ItemsProvider_Virtualize
p:
QuickGrid_ItemsProvider_Virtualize blazor .net 10 ,完整的示例代码,包括排序,查找
run ok
<h3>QuickGrid_ItemsProvider_Virtualize</h3>
@page "/StockDataPhysicalDisk/QuickGrid_ItemsProvider_Virtualize"
@using Microsoft.AspNetCore.Components.QuickGrid
@using System.Timers
@rendermode InteractiveServer
<PageTitle>QuickGrid 虚拟化示例</PageTitle>
<h3>员工名册 (总数据: 10,000 条)</h3>
<div class="search-container mb-3">
<label for="searchBox">查找: </label>
<!-- 使用 oninput 绑定实现实时搜索,通过 Timer 实现防抖 (Debounce) -->
<input id="searchBox"
type="text"
class="form-control d-inline-block w-auto"
placeholder="按姓名或职位搜索..."
@bind="SearchTerm"
@bind:event="oninput" />
</div>
@*
注意:开启 Virtualize="true" 时,必须为外层容器或 QuickGrid
指定固定的高度,并设置 overflow-y: auto,否则虚拟化无法计算可视区域!
*@
<div class="grid-container" style="height: 500px; overflow-y: auto; border: 1px solid #ccc;">
<QuickGrid TGridItem="Employee"
ItemsProvider="@employeeProvider"
Virtualize="true"
ItemSize="45"
@ref="grid">
<PropertyColumn Property="@(e => e.Id)" Title="编号" Sortable="true" />
<PropertyColumn Property="@(e => e.Name)" Title="姓名" Sortable="true" />
<PropertyColumn Property="@(e => e.Position)" Title="职位" Sortable="true" />
<PropertyColumn Property="@(e => e.Salary)" Title="薪水" Format="C0" Sortable="true" />
<PropertyColumn Property="@(e => e.JoinDate)" Title="入职日期" Format="yyyy-MM-dd" Sortable="true" />
</QuickGrid>
</div>
@code {
// 1. 组件引用和状态变量
private QuickGrid<Employee>? grid;
private GridItemsProvider<Employee>? employeeProvider;
private IQueryable<Employee> fakeDatabase = default!;
// 2. 搜索防抖定时器
private Timer? debounceTimer;
private string _searchTerm = string.Empty;
private string SearchTerm
{
get => _searchTerm;
set
{
_searchTerm = value;
// 每次输入改变时,重启定时器,避免频繁请求数据库
debounceTimer?.Stop();
debounceTimer?.Start();
}
}
protected override void OnInitialized()
{
// 模拟生成 10,000 条测试数据作为数据库
var list = new List<Employee>();
var positions = new[] { "前端开发", "后端开发", "产品经理", "测试工程师", "UI设计师" };
var random = new Random();
for (int i = 1; i <= 10000; i++)
{
list.Add(new Employee
{
Id = i,
Name = $"员工_{i}",
Position = positions[random.Next(positions.Length)],
Salary = random.Next(10000, 50000),
JoinDate = DateTime.Now.AddDays(-random.Next(1, 3650))
});
}
fakeDatabase = list.AsQueryable();
// 初始化防抖定时器 (300毫秒)
debounceTimer = new Timer(300);
debounceTimer.AutoReset = false;
debounceTimer.Elapsed += async (sender, args) =>
{
await InvokeAsync(async () =>
{
if (grid != null)
{
await grid.RefreshDataAsync(); // 触发 ItemsProvider 重新加载数据
}
});
};
// 3. 核心:配置 ItemsProvider 委托
employeeProvider = async request =>
{
var query = fakeDatabase;
// A. 执行过滤 (查找)
if (!string.IsNullOrWhiteSpace(_searchTerm))
{
query = query.Where(e => e.Name.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase) ||
e.Position.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase));
}
// B. 执行排序
if (request.SortByColumn != null)
{
// QuickGrid 会将列的 Title 传递过来,我们可以通过 Title 或 Property 名字来进行排序
var sortTitle = request.SortByColumn.Title;
var isAsc = request.SortByAscending;
query = sortTitle switch
{
"编号" => isAsc ? query.OrderBy(e => e.Id) : query.OrderByDescending(e => e.Id),
"姓名" => isAsc ? query.OrderBy(e => e.Name) : query.OrderByDescending(e => e.Name),
"职位" => isAsc ? query.OrderBy(e => e.Position) : query.OrderByDescending(e => e.Position),
"薪水" => isAsc ? query.OrderBy(e => e.Salary) : query.OrderByDescending(e => e.Salary),
"入职日期" => isAsc ? query.OrderBy(e => e.JoinDate) : query.OrderByDescending(e => e.JoinDate),
_ => query
};
}
else
{
// 默认排序
query = query.OrderBy(e => e.Id);
}
// C. 获取过滤后的总行数 (用于滚动条高度计算)
var totalCount = query.Count(); // 如果使用的是 EF Core,这里应使用 await query.CountAsync()
// D. 执行分页/虚拟化切片 (Skip 和 Take)
// request.StartIndex 和 request.Count 由 Virtualize 机制自动计算并提供
var pagedData = query.Skip(request.StartIndex)
.Take(request.Count ?? 50)
.ToList(); // 真实数据库中请使用 await query.ToListAsync()
// 模拟网络延迟 (可选,仅为展示加载效果)
await Task.Delay(100);
// E. 返回结果
return GridItemsProviderResult.From(items: pagedData, totalItemCount: totalCount);
};
}
public void Dispose()
{
debounceTimer?.Dispose();
}
// 数据模型
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Position { get; set; } = string.Empty;
public decimal Salary { get; set; }
public DateTime JoinDate { get; set; }
}
}
Custom styles
在 Blazor (.NET 10) 中控制 QuickGrid 的行/单元格背景色以及鼠标悬停(Hover)、光标手型(Cursor)效果
使用 Bootstrap 的 Class="table" 样式
<QuickGrid Class="table" Items="characters" Pagination="pagination" ">直接使用 Bootstrap 自带的悬停类
直接使用 Bootstrap 自带的斑马纹
<QuickGrid Class="table table-striped table-hover" Items="characters" Pagination="pagination" >
wwwroot/css/app.css
使用 ItemClass 动态条件样式
<QuickGrid Items="@items" ItemClass="@GetRowClass">
<PropertyColumn Property="@(p => p.Id)" Title="ID" />
<PropertyColumn Property="@(p => p.Name)" Title="名称" />
<PropertyColumn Property="@(p => p.Status)" Title="状态" />
</QuickGrid>
@code {
private IQueryable<ItemData> items = ...;
// 根据行的属性动态返回 CSS 类名
private string GetRowClass(ItemData item) => item.Status switch
{
"Warning" => "row-warning",
"Error" => "row-error",
_ => string.Empty
};
}
Theme="corporate"
自定义 CSS Variables 实现整体主题
<QuickGrid Items="@Data.People" Theme="corporate">https://www.yogihosting.com/blazor-quickgrid/
https://aspnet.github.io/quickgridsamples/styling
QuickGrid使用了 Bootstrap 的 Class="table" 样式
useful links
blazor
https://learn.microsoft.com/zh-cn/aspnet/core/blazor/components/quickgrid?view=aspnetcore-10.0&tabs=visual-studio-code
.net 8 quickgrid samples
https://aspnet.github.io/quickgridsamples/getstarted
