Skip to content

Conversation

@dariatiurina
Copy link
Contributor

@dariatiurina dariatiurina commented Feb 6, 2026

QuickGrid pagination support for SSR

Description

QuickGrid's Paginator component currently requires interactivity because it relies on @onclick event handlers, which are not supported by the StaticHtmlRenderer. Additionally, PaginationState stores the current page index in memory, so the state cannot be persisted across SSR requests or shared via URL.

This PR adds SSR support to the Paginator component so that pagination works without interactivity.

Problem

There were two main blockers preventing the Paginator from working in SSR:

  1. @onclick is not supported in SSR — The StaticHtmlRenderer does not render event handler attributes like @onclick, so the pagination buttons had no effect.
  2. In-memory page statePaginationState stores the current page index in memory, which is lost between SSR requests. Users also cannot share URLs pointing to a specific page.

Considered solutions

  • Session / TempData - Persist page state server-side, keeping public APIs unchanged. Discarded, because still doesn't allow URL sharing between users.
  • Query string parameter - Persist the current page as a ?page=N query parameter in the URL . This option was chosen, because it provides a familiar UX (?page=2), enables link sharing, and works with both SSR and interactive rendering.

Implemented changes

Paginator rendering (Paginator.razor)

  • Uses RendererInfo.IsInteractive to branch between interactive and SSR rendering.
  • Interactive mode: Unchanged — continues to use @onclick handlers on <button> elements.
  • SSR mode: Each navigation button is wrapped in a <form> with method="get", data-enhance, @formname, and @onsubmit to handle page navigation via form submission. Each form includes an <AntiforgeryToken />.

Paginator logic (Paginator.razor.cs)

  • New QueryName parameter ([Parameter, DisallowNull], defaults to "page") — specifies the query string key used to persist the current page index in the URL.
  • NavigationManager injection — used to read the current URL and navigate with updated query parameters.
  • OnParametersSetOnParametersSetAsync — changed to async to support reading the query string on first render and setting the initial page index from it.
  • ReadPageIndexFromQueryString() — parses the URL query string using QueryStringEnumerable to extract the 1-based page number and convert it to a 0-based index.
  • GoToPageAsync() — now also updates the URL via NavigationManager.GetUriWithQueryParameter() and NavigationManager.NavigateTo() after setting the page index.
  • Form names — generated per QueryName (e.g., Paginator_page_GoFirst) to support multiple paginators on the same page with different query parameter names.

Project references (Microsoft.AspNetCore.Components.QuickGrid.csproj)

  • Added QueryStringEnumerable.cs as a shared source compile include.
  • Added a reference to Microsoft.AspNetCore.Components.Endpoints.

Public API changes (PublicAPI.Unshipped.txt)

+Microsoft.AspNetCore.Components.QuickGrid.Paginator.QueryName.get -> string!
+Microsoft.AspNetCore.Components.QuickGrid.Paginator.QueryName.set -> void
+override Microsoft.AspNetCore.Components.QuickGrid.Paginator.OnParametersSetAsync() -> System.Threading.Tasks.Task!
*REMOVED*override Microsoft.AspNetCore.Components.QuickGrid.Paginator.OnParametersSet() -> void

Breaking change

When multiple Paginator components are used on the same page, each one now must have a unique QueryName value. Since QueryName defaults to "page", having two or more paginators without explicitly setting different QueryName values will cause them to share the same query parameter and interfere with each other. This applies to both SSR and interactive rendering.

Before (worked implicitly):

<Paginator State="pagination1" />
<Paginator State="pagination2" />

After (requires unique QueryName per paginator):

<Paginator State="pagination1" />
<Paginator State="pagination2" QueryName="page2" />

Usage example

@using Microsoft.AspNetCore.Components.QuickGrid

<QuickGrid Items="items" Pagination="pagination">
    <PropertyColumn Property="@(p => p)" Title="Number" />
</QuickGrid>
<Paginator State="pagination" />

@* Multiple paginators on the same page with different query names *@
<QuickGrid Items="items" Pagination="pagination2">
    <PropertyColumn Property="@(p => p)" Title="Number" />
</QuickGrid>
<Paginator State="pagination2" QueryName="page2" />

@code {
    PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
    PaginationState pagination2 = new PaginationState { ItemsPerPage = 5 };
    private IQueryable<int> items = Enumerable.Range(1, 100).AsQueryable();
}

Fixes #51249 (partially)

@github-actions github-actions bot added the area-blazor Includes: Blazor, Razor Components label Feb 6, 2026
@dariatiurina dariatiurina self-assigned this Feb 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QuickGrid support for static server-side rendering (static SSR)

1 participant