ASP.NET Core BlazorQuickGrid 组件

QuickGrid 组件是一个 Razor 组件,用于以表格格式快速高效地显示数据。 QuickGrid 为常见网格呈现方案提供简单方便的数据网格组件,并充当构建数据网格组件的参考体系结构和性能基线。 QuickGrid 进行了高度的优化,并使用高级技术实现最佳呈现性能。

程序包

Microsoft.AspNetCore.Components.QuickGrid 包添加包引用。

注意

有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。

示例应用

有关各种 QuickGrid 演示,请参阅面向 Blazor 示例应用的快速入门。 演示网站托管在 GitHub Pages 上。 由于使用社区维护的 BlazorWasmPrerendering.Build GitHub 项目进行静态预呈现,因此站点加载速度较快。

QuickGrid 实现

若要实现 QuickGrid 组件,请执行以下操作:

  • 在 Razor 标记 (<QuickGrid>...</QuickGrid>) 中指定 QuickGrid 组件的标记。
  • 为网格命名可查询数据源。 使用以下任一数据源:
    • Items:可以为 null 的 IQueryable<TGridItem>,其中 TGridItem 是网格中每一行表示的数据类型。
    • ItemsProvider:为网格提供数据的回调。
  • Class:可选的 CSS 类名称。 如果已提供,则类名称将包含在呈现的表的 class 属性中。
  • Theme:主题名称(默认值:default)。 这会影响哪些样式规则与表匹配。
  • Virtualize:如果为 true,则网格会使用虚拟化呈现。 这通常与滚动结合使用,导致网格仅提取和呈现当前滚动视区周围的数据。 这可以极大地提高滚动浏览大型数据集时的性能。 如果使用 Virtualize,则应为 ItemSize 提供值,并且必须确保每一行以恒定的高度呈现。 通常,如果呈现的数据量较小或使用的是分页,最好不要使用 Virtualize
  • ItemSize:仅当使用 Virtualize 时才适用。 ItemSize 定义每一行的预期高度(以像素为单位),使虚拟化机制能够提取正确数量的项,以匹配显示大小并确保准确滚动。
  • ItemKey:(可选)在呈现的每一行上为 @key 定义值。 通常,这用于为每个数据项指定唯一标识符,例如主键值。 这样,网格就可以根据唯一标识符保留行元素和数据项之间的关联,即使 TGridItem 实例被新副本替换(例如,在对基础数据存储执行新查询之后)也是如此。 如果未设置,则 @keyTGridItem 实例。
  • OverscanCount:定义在可见区域前后呈现以减少滚动期间的呈现频率的其他项数。 虽然较高的值可通过在屏幕外呈现更多项来提高滚动平滑度,但较高的值也会导致初始加载时间增加。 建议根据数据集大小和用户体验要求找到平衡点。 默认值为 3。 仅在使用 Virtualize 时可用。
  • Pagination:(可选)将此 TGridItem 实例与 PaginationState 模型链接,使网格仅提取并呈现数据的当前页。 这通常与 Paginator 组件或显示和更新提供的 PaginationState 实例的一些其他 UI 逻辑结合使用。
  • QuickGrid 子内容 (RenderFragment) 中,指定 PropertyColumn<TGridItem,TProp>,表示单元格显示值的 TGridItem 列:
  • 在 Razor 标记 (<QuickGrid>...</QuickGrid>) 中指定 QuickGrid 组件的标记。
  • 为网格命名可查询数据源。 使用以下任一数据源:
    • Items:可以为 null 的 IQueryable<TGridItem>,其中 TGridItem 是网格中每一行表示的数据类型。
    • ItemsProvider:为网格提供数据的回调。
  • Class:可选的 CSS 类名称。 如果已提供,则类名称将包含在呈现的表的 class 属性中。
  • Theme:主题名称(默认值:default)。 这会影响哪些样式规则与表匹配。
  • Virtualize:如果为 true,则网格会使用虚拟化呈现。 这通常与滚动结合使用,导致网格仅提取和呈现当前滚动视区周围的数据。 这可以极大地提高滚动浏览大型数据集时的性能。 如果使用 Virtualize,则应为 ItemSize 提供值,并且必须确保每一行以恒定的高度呈现。 通常,如果呈现的数据量较小或使用的是分页,最好不要使用 Virtualize
  • ItemSize:仅当使用 Virtualize 时才适用。 ItemSize 定义每一行的预期高度(以像素为单位),使虚拟化机制能够提取正确数量的项,以匹配显示大小并确保准确滚动。
  • ItemKey:(可选)在呈现的每一行上为 @key 定义值。 通常,这用于为每个数据项指定唯一标识符,例如主键值。 这样,网格就可以根据唯一标识符保留行元素和数据项之间的关联,即使 TGridItem 实例被新副本替换(例如,在对基础数据存储执行新查询之后)也是如此。 如果未设置,则 @keyTGridItem 实例。
  • Pagination:(可选)将此 TGridItem 实例与 PaginationState 模型链接,使网格仅提取并呈现数据的当前页。 这通常与 Paginator 组件或显示和更新提供的 PaginationState 实例的一些其他 UI 逻辑结合使用。
  • QuickGrid 子内容 (RenderFragment) 中,指定 PropertyColumn<TGridItem,TProp>,表示单元格显示值的 TGridItem 列:

例如,添加以下组件以呈现网格。

对于 Blazor Web 应用,QuickGrid 组件必须采用交互式呈现模式才能启用分页和排序等交互式功能。

PromotionGrid.razor

@page "/promotion-grid"
@using Microsoft.AspNetCore.Components.QuickGrid

<PageTitle>Promotion Grid</PageTitle>

<h1>Promotion Grid Example</h1>

<QuickGrid Items="people">
    <PropertyColumn Property="@(p => p.PersonId)" Sortable="true" />
    <PropertyColumn Property="@(p => p.Name)" Sortable="true" />
    <PropertyColumn Property="@(p => p.PromotionDate)" Format="yyyy-MM-dd" Sortable="true" />
</QuickGrid>

@code {
    private record Person(int PersonId, string Name, DateOnly PromotionDate);

    private IQueryable<Person> people = new[]
    {
        new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)),
        new Person(10944, "António Langa", new DateOnly(1991, 12, 1)),
        new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)),
        new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)),
        new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)),
        new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)),
    }.AsQueryable();
}

在浏览器中的相对路径 /promotion-grid 处访问组件。

目前没有使用完全成熟的商业网格倾向于提供的功能扩展 QuickGrid 的计划,例如分层行、拖动以重新排序列或类似于 Excel 的范围选择。 如果需要不希望自行开发的高级功能,请继续使用第三方网格。

按列排序

QuickGrid 组件可以按列对项进行排序。 在 Blazor Web 应用中,排序要求组件采用交互式呈现模式

Sortable="true" (Sortable) 添加到 PropertyColumn<TGridItem,TProp> 标记:

<PropertyColumn Property="..." Sortable="true" />

在正在运行的应用中,通过选择呈现的列标题对 QuickGrid 列进行排序。

包含 Paginator 组件的页项

QuickGrid 组件可以从数据源中对数据进行分页。 在 Blazor Web 应用中,分页要求组件采用交互式呈现模式

PaginationState 实例添加到组件的 @code 块中。 将 ItemsPerPage 设置为每页要显示的项数。 在以下示例中,实例命名为 pagination,每页项数设置为 10:

PaginationState pagination = new PaginationState { ItemsPerPage = 10 };

QuickGrid 组件的 Pagination 属性设置为 pagination

<QuickGrid Items="..." Pagination="pagination">

若要提供分页的 UI,请在 QuickGrid 组件上方和/或下方添加 Paginator 组件 。 将 Paginator.State 设置为 pagination

<Paginator State="pagination" />

在正在运行的应用中,使用呈现的 Paginator 组件分页浏览项。

自定义属性和样式

QuickGrid 还支持将自定义属性和样式类 (Class) 传递给呈现的表元素:

<QuickGrid Items="..." custom-attribute="value" Class="custom-class">

Entity Framework Core (EF Core) 数据源

使用工厂模式解析向 QuickGrid 组件提供数据的 EF Core 数据库上下文。 若要详细了解为什么推荐使用工厂模式,请参阅使用 Entity Framework Core (EF Core) 的 ASP.NET Core Blazor

使用 @inject 指令将数据库上下文工厂 (IDbContextFactory<TContext>) 注入组件。 工厂方法需要处置数据库上下文,因此组件使用 @implements 指令实现 IAsyncDisposable 接口。 QuickGrid 组件的项提供程序是一个 DbSet<T>,从注入的数据库上下文工厂已创建的数据库上下文 (CreateDbContext) 获取。

QuickGrid 可识别 EF 提供的 IQueryable 实例,并知道如何异步解析查询以提高效率。

添加 Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter NuGet 包的包引用。

注意

有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。

调用 Program 文件中服务集合上的 AddQuickGridEntityFrameworkAdapter,以注册 EF 感知 IAsyncQueryExecutor 实现:

builder.Services.AddQuickGridEntityFrameworkAdapter();

以下示例使用 AppDbContext 数据库上下文 (context) 中的 ExampleTable DbSet<TEntity>(表)作为 QuickGrid 组件的数据源:

@using Microsoft.AspNetCore.Components.QuickGrid
@using Microsoft.EntityFrameworkCore
@implements IAsyncDisposable
@inject IDbContextFactory<AppDbContext> DbFactory

...

<QuickGrid ... Items="context.ExampleTable" ...>
    ...
</QuickGrid>

@code {
    private AppDbContext context = default!;

    protected override void OnInitialized()
    {
        context = DbFactory.CreateDbContext();
    }

    public async ValueTask DisposeAsync() => await context.DisposeAsync();
}

在上述示例的代码块 (@code) 中:

  • context 字段保留数据库上下文,作为 AppDbContext 键入。
  • OnInitialized 生命周期方法将新的数据库上下文 (CreateDbContext) 分配给注入工厂 (DbFactory) 中的 context 字段。
  • 异步 DisposeAsync 方法在组件被释放时释放数据库上下文。

还可以使用任何 EF 支持的 LINQ 运算符来筛选数据,然后再将其传递给 Items 参数。

以下示例按在搜索框中输入的电影标题筛选电影。 数据库上下文是 BlazorWebAppMoviesContext,模型是 Movie。 电影的 Title 属性用于筛选操作。

@using Microsoft.AspNetCore.Components.QuickGrid
@using Microsoft.EntityFrameworkCore
@implements IAsyncDisposable
@inject IDbContextFactory<BlazorWebAppMoviesContext> DbFactory

...

<p>
    <input type="search" @bind="titleFilter" @bind:event="oninput" />
</p>

<QuickGrid ... Items="FilteredMovies" ...>
    ...
</QuickGrid>

@code {
    private string titleFilter = string.Empty;
    private BlazorWebAppMoviesContext context = default!;

    protected override void OnInitialized()
    {
        context = DbFactory.CreateDbContext();
    }

    private IQueryable<Movie> FilteredMovies => 
        context.Movie.Where(m => m.Title!.Contains(titleFilter));

    public async ValueTask DisposeAsync() => await context.DisposeAsync();
}

显示名称支持

可以使用 PropertyColumn<TGridItem,TProp> 的标记中的 ColumnBase<TGridItem>.Title 来分配列标题。 在下面的电影示例中,为列的影片发布日期数据指定了名称“Release Date”:

<PropertyColumn Property="movie => movie.ReleaseDate" Title="Release Date" />

但是,要维护应用,更好的选择是从绑定模型属性管理列标题(名称)。 模型可以使用 [Display] 特性控制属性的显示名称。 在以下示例中,模型为其 ReleaseDate 属性指定影片发布日期显示名称“Release Date”:

[Display(Name = "Release Date")]
public DateTime ReleaseDate { get; set; }

若要使 QuickGrid 组件能够使用 DisplayAttribute.Name,请将 PropertyColumn<TGridItem,TProp> 设置为组件或单独的类中的子类:

public class DisplayNameColumn<TGridItem, TProp> : PropertyColumn<TGridItem, TProp>
{
    protected override void OnParametersSet()
    {
        if (Title is null && Property.Body is MemberExpression memberExpression)
        {
            var memberInfo = memberExpression.Member;
            Title = 
                memberInfo.GetCustomAttribute<DisplayNameAttribute>().DisplayName ??
                memberInfo.GetCustomAttribute<DisplayAttribute>().Name ??
                memberInfo.Name;
        }

        base.OnParametersSet();
    }
}

使用 QuickGrid 组件中的子类。 在以下示例中,使用了上述 DisplayNameColumn。 名称“Release Date”由模型中的 [Display] 特性提供,因此无需指定 Title

<DisplayNameColumn Property="movie => movie.ReleaseDate" />

还支持 [DisplayName] 特性

[DisplayName("Release Date")]
public DateTime ReleaseDate { get; set; }

但是,建议使用 [Display] 特性,因为它使其他属性可用。 例如,通过 [Display] 特性可以为本地化分配资源类型。

远程数据

在 Blazor WebAssembly 应用中,从服务器上的基于 JSON 的 Web API 提取数据是一个常见要求。 若要仅提取当前页/数据视区所需的数据,并在服务器上应用排序或筛选规则,请使用 ItemsProvider 参数。

在服务器端 Blazor 应用中,如果需要应用来查询外部终结点,或者在 IQueryable 无法满足要求的其他情况下,也可以使用 ItemsProvider

提供与 GridItemsProvider<TGridItem> 委托类型匹配的回调,其中 TGridItem 是网格中显示的数据类型。 该回调被赋予一个 GridItemsProviderRequest<TGridItem> 类型的参数,它指定了要返回的数据的起始索引、最大行计数和排序顺序。 除了返回匹配项之外,分页和虚拟化还需要总项计数 (totalItemCount) 才能正常运行。

以下示例从公开的 OpenFDA Food Enforcement 数据库获取数据。

GridItemsProvider<TGridItem>GridItemsProviderRequest<TGridItem> 转换为针对 OpenFDA 数据库的查询。 查询参数转换为外部 JSON API 支持的特定 URL 格式。 只能通过外部 API 支持的排序和筛选来执行排序和筛选。 OpenFDA 终结点不支持排序,因此没有任何列标记为可排序。 但是,它确实支持跳过记录(skip 参数)和限制返回记录的数量(limit 参数),以便组件可以启用虚拟化,并快速滚动浏览数万条记录。

FoodRecalls.razor

@page "/food-recalls"
@inject HttpClient Http
@inject NavigationManager NavManager

<PageTitle>Food Recalls</PageTitle>

<h1>OpenFDA Food Recalls</h1>

<div class="grid" tabindex="-1">
    <QuickGrid ItemsProvider="@foodRecallProvider" Virtualize="true">
        <PropertyColumn Title="ID" Property="@(c => c.Event_Id)" />
        <PropertyColumn Property="@(c => c.State)" />
        <PropertyColumn Property="@(c => c.City)" />
        <PropertyColumn Title="Company" Property="@(c => c.Recalling_Firm)" />
        <PropertyColumn Property="@(c => c.Status)" />
    </QuickGrid>
</div>

<p>Total: <strong>@numResults results found</strong></p>

@code {
    GridItemsProvider<FoodRecall>? foodRecallProvider;
    int numResults;

    protected override async Task OnInitializedAsync()
    {
        foodRecallProvider = async req =>
        {
            var url = NavManager.GetUriWithQueryParameters(
                "https://api.fda.gov/food/enforcement.json", 
                new Dictionary<string, object?>
            {
                { "skip", req.StartIndex },
                { "limit", req.Count },
            });

            var response = await Http.GetFromJsonAsync<FoodRecallQueryResult>(
                url, req.CancellationToken);

            return GridItemsProviderResult.From(
                items: response!.Results,
                totalItemCount: response!.Meta.Results.Total);
        };

        numResults = (await Http.GetFromJsonAsync<FoodRecallQueryResult>(
            "https://api.fda.gov/food/enforcement.json"))!.Meta.Results.Total;
    }
}

有关调用 Web API 的详细信息,请参阅在 ASP.NET Core Blazor 应用中调用 Web API

QuickGrid 基架

QuickGrid 基架用于为包含 QuickGrid 的 Razor 组件搭建基架,以显示数据库中的数据。

基架基于 Entity Framework Core 数据模型生成基本的创建、读取、更新和删除 (CRUD) 页。 可以搭建单个页面或所有 CRUD 页面。 选择模型类和 DbContext,根据需要创建新的 DbContext

搭建的 Razor 组件会添加到生成的文件夹(以模型类命名)中项目的文件夹内。 生成的 Index 组件使用 QuickGrid 组件显示数据。 根据需要自定义生成的组件,并使交互性能够利用交互式功能,例如分页排序和筛选。

基架生成的组件需要服务器端呈现 (SSR),因此在 WebAssembly 上运行时不支持它们。

右键单击 Components/Pages 文件夹,然后选择“添加”>“已搭建基架的新项”

打开“添加新基架项”对话框,转到“已安装”>“通用”>“Razor 组件”,选择“使用实体框架(CRUD)的 Razor 组件”。 选择“添加”按钮。

完成“添加使用实体框架(CRUD)的 Razor 组件”对话框

  • “模板”下拉列表包含用于专门创建“编辑”、“创建”、“删除”、“详细信息”和“列出”组件的其他模板。 如果只需要创建已搭建为某个模型类的特定组件类型,此下拉列表就非常有用。 将“模板”下拉列表设置为“CRUD”,以搭建一组完整的组件
  • 在“模型类”下拉列表中,选择模型类。 根据模型名称为生成的组件创建一个文件夹(如果模型类命名为 Movie,则文件夹会自动命名为 MoviePages)。
  • 对于 DbContext 类,请选择现有数据库上下文或依次选择“+”(加号)按钮和“添加数据上下文”模式对话框来添加新数据库上下文
  • 模型对话框关闭后,“数据库提供程序”下拉列表默认为 SQL Server。 可以为正在使用的数据库选择适当的提供程序。 这些选项包括 SQL Server、SQLite、PostgreSQL 和 Azure Cosmos DB。
  • 选择 添加