Open
Conversation
Persist pagination and sort state in URL query string parameters so QuickGrid works without interactivity. In SSR mode, buttons render as enhanced forms with antiforgery tokens instead of @OnClick handlers.
92bb4b5 to
1d9c7a7
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Adds static SSR (non-interactive) support to QuickGrid pagination and column sorting by persisting state in the URL query string and rendering SSR-compatible form posts for interactions.
Changes:
- Introduces
QuickGrid<TGridItem>.QueryNameand URL query-string synchronization for sort state (including back/forward handling). - Updates
Paginatorand sortable column headers to render SSR-friendly<form method="post" data-enhance ...>interactions with antiforgery. - Adds E2E coverage for no-interactivity pagination/sorting scenarios plus supporting test pages/components.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.cs | Adds QueryName, query-string sort persistence, and navigation event handling/disposal. |
| src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Columns/ColumnBase.razor | SSR branch for sortable headers using form posts; wires ColumnIndex from grid. |
| src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Columns/ColumnBase.razor.cs | Stores ColumnIndex and derives SSR form name from QueryName. |
| src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Pagination/Paginator.razor | SSR branch for pagination buttons using form posts + antiforgery. |
| src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Pagination/Paginator.razor.cs | Reads/writes current page to query string; listens to navigation changes. |
| src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Pagination/PaginationState.cs | Adds internal QueryName and clamps page index based on known bounds. |
| src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Infrastructure/QueryStringHelper.cs | Adds helper to read first matching query parameter via QueryStringEnumerable. |
| src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Microsoft.AspNetCore.Components.QuickGrid.csproj | Includes shared QueryStringEnumerable.cs in compilation. |
| src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/PublicAPI.Unshipped.txt | Declares new/changed public API surface for shipping. |
| src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/GridRaceConditionTest.cs | Registers a test NavigationManager for new injection usage. |
| src/Components/test/E2ETest/Tests/QuickGridNoInteractivityTest.cs | New E2E tests validating SSR paging/sorting + URL persistence. |
| src/Components/test/E2ETest/Tests/QuickGridTest.cs | Adds interactive-mode dual-paginator coverage. |
| src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/QuickGrid/QuickGridComponent.razor | Adds SSR test page with multiple grids/paginators and distinct QueryNames. |
| src/Components/test/testassets/Components.TestServer/Components.TestServer.csproj | Adds QuickGrid reference needed by the new test page. |
| src/Components/test/testassets/BasicTestApp/QuickGridTest/QuickGridDualPaginatorComponent.razor | Adds interactive test component for dual paginators. |
| src/Components/test/testassets/BasicTestApp/Index.razor | Adds menu entry for the new interactive test component. |
src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Columns/ColumnBase.razor
Outdated
Show resolved
Hide resolved
src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.cs
Show resolved
Hide resolved
...st/testassets/Components.TestServer/RazorComponents/Pages/QuickGrid/QuickGridComponent.razor
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Contributor
Author
|
It is also a fix for #57289 |
.../testassets/Components.TestServer/RazorComponents/Pages/QuickGrid/QuickGridInteractive.razor
Show resolved
Hide resolved
...onents/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Pagination/Paginator.razor.cs
Show resolved
Hide resolved
...onents/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Pagination/PaginationState.cs
Show resolved
Hide resolved
src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.cs
Show resolved
Hide resolved
Contributor
|
Looks like this PR hasn't been active for some time and the codebase could have been changed in the meantime. |
ilonatommy
added a commit
that referenced
this pull request
Mar 20, 2026
) These tests were unquarantined in #65864 but are failing again across multiple PRs (#64964, #65871, #65451) in the Helix x64 Subset 2 job. Re-quarantining only the RazorRuntimeCompilationHostingStartupTest methods, not the RazorBuildTest ones. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ilonatommy
added a commit
that referenced
this pull request
Mar 20, 2026
) (#65881) These tests were unquarantined in #65864 but are failing again across multiple PRs (#64964, #65871, #65451) in the Helix x64 Subset 2 job. Re-quarantining only the RazorRuntimeCompilationHostingStartupTest methods, not the RazorBuildTest ones. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
QuickGrid SSR support
Description
QuickGrid'sPaginatorand column sorting currently require interactivity because they rely on@onclickevent handlers and in-memory state, which don't work with theStaticHtmlRenderer. Page index and sort state are stored in memory, so they're lost between SSR requests and can't be shared via URL.This PR adds SSR support to both the
Paginatorcomponent and column sorting inQuickGrid, so that pagination and sorting work without interactivity by persisting state in the URL query string using regular<a>links.Problem
There were three main blockers preventing
QuickGridfrom working fully in SSR:@onclickis not supported in SSR — TheStaticHtmlRendererdoes not render event handler attributes like@onclick, so the pagination buttons and sort column headers had no effect.PaginationStatestores the current page index in memory, which is lost between SSR requests. Users also cannot share URLs pointing to a specific page.QuickGrid, which is also lost between SSR requests.Considered solutions
Session/TempData— Persist state server-side, keeping public APIs unchanged. Discarded, because it still doesn't allow URL sharing between users.?page=N,?sort=ColumnTitle,?order=asc/desc. This option was chosen because it provides a familiar UX, enables link sharing, and works with both SSR and interactive rendering.Implemented changes
QuickGrid (
QuickGrid.razor.cs)QueryNameparameter ([Parameter], defaults to"") — specifies a prefix for query string parameter names used to persist pagination and sort state. When empty, parameters are namedpage,sort, andorder. When set (e.g.,"products"), parameters becomeproducts_page,products_sort, andproducts_order. This allows multiple grids on the same page without URL parameter conflicts.NavigationManagerinjection — used to read the current URL and navigate with updated query parameters.QueryParameterValueSupplier— used to parse query string values from the current URL.OnInitialized— parses the initial URL query string and subscribes toNavigationManager.LocationChangedto react to URL changes (e.g., browser back/forward).OnParametersSetAsync— propagates the page query parameter name to the associatedPaginationStateso thePaginatoruses the correct parameter name.ReadSortFromQueryString()— parses?sort=ColumnTitle&order=ascfrom the URL usingQueryParameterValueSupplierand caches the result for use during column collection. The sort column is identified by itsTitleproperty.GetSortUrl()/GetSortQueryStringUrl()— generates URLs with updated sort query parameters usingNavigationManager.GetUriWithQueryParameters().OnLocationChanged— responds to external URL changes (back/forward navigation) by re-reading sort state and refreshing data. Falls back to the default sort column/direction when sort parameters are removed from the URL._defaultSortColumn,_defaultSortAscending) so that removing sort params from the URL restores the original sort order.DisposeAsync— updated to unsubscribe fromLocationChanged.Column sorting (
ColumnBase.razor/ColumnBase.razor.css)<button>to<a>— each sortable column header now renders as an<a>element with anhrefpointing to a URL with updated sort query parameters (viaGrid.GetSortUrl(this)). This works in both SSR and interactive rendering without branching.Title— sorting is only rendered when the column has a non-nullTitle(needed to identify the column in query parameters).button.col-titletoa.col-title, withtext-decoration: noneandcolor: inheritadded for link styling consistency.Paginator rendering (
Paginator.razor/Paginator.razor.css)<button>to<a>— each pagination button (first, previous, next, last) is now an<a>element with anhrefpointing to the URL for the target page. Disabled state usesaria-disabled="true"andpointer-events: noneinstead of the HTMLdisabledattribute.nav buttontonav a, witharia-disabledselectors replacing[disabled].Paginator logic (
Paginator.razor.cs)NavigationManagerinjection — used to generate page URLs viaGetUriWithQueryParameter().QueryParameterValueSupplier— used to parse the current page from the URL query string.OnInitialized— subscribes toNavigationManager.LocationChanged.OnParametersSet→OnParametersSetAsync— changed to async to support reading the query string on first render and setting the initial page index.ReadPageIndexFromQueryString()— parses the 1-based page number from the URL and converts it to a 0-based index.GetPageUrl()— generates URLs for pagination links. Page 1 omits the query parameter (clean URL); other pages use?page=N(1-based).OnLocationChanged— responds to external URL changes (back/forward navigation) by re-reading page state and updating accordingly.Dispose— unsubscribes fromLocationChanged.PaginationState (
PaginationState.cs)QueryNameinternal property — set byQuickGridso thePaginatorreads the correct query parameter name (e.g.,pageorproducts_page).Shared infrastructure changes
QueryParameterValueSupplier.cs,QueryParameterNameComparer.cs,StringSegmentAccumulator.cs, andUrlValueConstraint.csmoved fromComponents/src/Routing/toShared/src/so they can be consumed by both the Components project and QuickGrid.QueryParameterValueSupplier.GetQueryString()— new static method that extracts the query string portion from a URL. Previously a local static method inSupplyParameterFromQueryValueProvider, now shared.QueryStringEnumerable.cs— added as a shared source compile include in the QuickGrid project.Project references
Microsoft.AspNetCore.Components.csproj— added shared source includes for the moved routing files.Microsoft.AspNetCore.Components.QuickGrid.csproj— added shared source includes forQueryParameterValueSupplier,QueryParameterNameComparer,StringSegmentAccumulator,UrlValueConstraint, andQueryStringEnumerable.Public API changes (
PublicAPI.Unshipped.txt)Breaking change
The sort column headers and paginator buttons have changed from
<button>elements to<a>(link) elements. Any custom CSS targetingbutton.col-titleornav buttonin the paginator will need to be updated to targeta.col-titleornav arespectively.Additionally, when multiple
QuickGridcomponents are used on the same page, each one now must have a uniqueQueryNamevalue. SinceQueryNamedefaults to"", having two or more grids without explicitly setting differentQueryNamevalues will cause them to share the same query parameters (page,sort,order) for pagination and sorting, and interfere with each other. This applies to both SSR and interactive rendering.Before (worked implicitly):
After (requires unique
QueryNameper grid):Shared
PaginationStatelimitationEach
QuickGridpropagates its page query parameter name to the associatedPaginationStateduringOnParametersSetAsync, so that the connectedPaginatoruses the same query parameter name. This means multipleQuickGridcomponents must not share the samePaginationStateinstance if they have differentQueryNamevalues — the last grid to render will overwrite the query name on the shared state, causing thePaginatorto read from the wrong query parameter. Each grid should have its ownPaginationStateinstance.Usage example
Fixes #51249