Skip to content

QuickGrid SSR support#65451

Open
dariatiurina wants to merge 16 commits intodotnet:mainfrom
dariatiurina:51249-quickgrid-ssr-sorting
Open

QuickGrid SSR support#65451
dariatiurina wants to merge 16 commits intodotnet:mainfrom
dariatiurina:51249-quickgrid-ssr-sorting

Conversation

@dariatiurina
Copy link
Copy Markdown
Contributor

@dariatiurina dariatiurina commented Feb 17, 2026

QuickGrid SSR support

Description

QuickGrid's Paginator and column sorting currently require interactivity because they rely on @onclick event handlers and in-memory state, which don't work with the StaticHtmlRenderer. 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 URL-driven sorting and pagination to QuickGrid so that both features work without interactivity (SSR) by persisting state in the URL query string using regular <a> links. The feature is controlled by an AppContext switch and is enabled by default.

Problem

There were three main blockers preventing QuickGrid from working fully in SSR:

  1. @onclick is not supported in SSR — The StaticHtmlRenderer does not render event handler attributes like @onclick, so the pagination buttons and sort column headers 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.
  3. In-memory sort state — The current sort column and direction are stored in memory on 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.
  • Query string parameters — Persist state in the URL as ?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.

Feature flag

The URL-based navigation behavior is controlled by an AppContext switch:

Microsoft.AspNetCore.Components.QuickGrid.EnableUrlBasedQuickGridNavigationAndSorting
  • Enabled by default (true) — sorting and pagination use <a> links with URL query parameters.
  • Opt-out — set the switch to false to revert to the previous <button>-based behavior with @onclick handlers.

When the feature flag is disabled, QuickGrid and Paginator render and behave exactly as they did before this change, using buttons and in-memory state.

Implemented changes

QuickGrid (QuickGrid.razor.cs)

  • New QueryParameterNamePrefix parameter ([Parameter], defaults to "") — specifies a prefix for query string parameter names used to persist pagination and sort state. When empty, parameters are named page, sort, and order. When set (e.g., "products"), parameters become products_page, products_sort, and products_order. This allows multiple grids on the same page without URL parameter conflicts.
  • NavigationManager injection — used to read the current URL and navigate with updated query parameters.
  • QueryParameterValueSupplier — used to parse query string values from the current URL.
  • OnInitialized — when the feature flag is enabled, parses the initial URL query string and subscribes to NavigationManager.LocationChanged to react to URL changes (e.g., browser back/forward).
  • OnParametersSetAsync — propagates the page query parameter name to the associated PaginationState so the Paginator uses the correct parameter name.
  • ReadSortFromQueryString() — parses ?sort=ColumnTitle&order=asc from the URL using QueryParameterValueSupplier and caches the result for use during column collection. The sort column is identified by its Title property.
  • GetSortUrl() / GetSortQueryStringUrl() — generates URLs with updated sort query parameters using NavigationManager.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.
  • Default sort tracking — stores the initial default sort column and direction (_defaultSortColumn, _defaultSortAscending) so that removing sort params from the URL restores the original sort order.
  • SortByColumnAsync — when the feature flag is enabled, navigates to the new sort URL instead of just calling StateHasChanged().
  • DisposeAsync — updated to unsubscribe from LocationChanged.

Column sorting (ColumnBase.razor / ColumnBase.razor.css)

  • Conditional rendering based on feature flag — when EnableUrlBasedQuickGridNavigationAndSorting is enabled and the column has a non-null Title, sortable column headers render as <a> elements with an href pointing to a URL with updated sort query parameters (via Grid.GetSortUrl(this)). When the flag is disabled, the previous <button> with @onclick is rendered. Columns with a null Title fall back to a plain <div>.
  • CSS updated — added a.col-title selectors alongside existing button.col-title, with text-decoration: none and color: inherit for link styling consistency.

Paginator rendering (Paginator.razor / Paginator.razor.css)

  • Conditional rendering based on feature flag — when enabled, pagination navigation uses <a> elements with href pointing to the URL for the target page. Disabled state uses aria-disabled="true" and pointer-events: none. When disabled, the previous <button> with @onclick and disabled attribute is rendered.
  • CSS updated — added nav a selectors alongside existing nav button, with aria-disabled selectors added alongside :disabled.

Paginator logic (Paginator.razor.cs)

  • NavigationManager injection — used to generate page URLs via GetUriWithQueryParameter().
  • QueryParameterValueSupplier — used to parse the current page from the URL query string.
  • OnInitialized — when the feature flag is enabled, subscribes to NavigationManager.LocationChanged.
  • OnParametersSetOnParametersSetAsync — changed to async to support reading the query string on first render and setting the initial page index (when the feature flag is enabled).
  • 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 from LocationChanged when the feature flag is enabled.

PaginationState (PaginationState.cs)

  • QueryName internal property — set by QuickGrid so the Paginator reads the correct query parameter name (e.g., page or products_page).

Feature flag (QuickGridFeatureFlags.cs)

  • New internal static class that reads the Microsoft.AspNetCore.Components.QuickGrid.EnableUrlBasedQuickGridNavigationAndSorting AppContext switch. Defaults to true (enabled) when the switch is not set.

Shared infrastructure changes

  • Moved to shared sourceQueryParameterValueSupplier.cs, QueryParameterNameComparer.cs, StringSegmentAccumulator.cs, and UrlValueConstraint.cs moved from Components/src/Routing/ to Shared/src/ so they can be consumed by both the Components project and QuickGrid.
  • QueryParameterValueSupplier.GetQueryString() — new public static method that extracts the query string portion from a URL. Previously a local static method in SupplyParameterFromQueryValueProvider, 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 for QueryParameterValueSupplier, QueryParameterNameComparer, StringSegmentAccumulator, UrlValueConstraint, and QueryStringEnumerable.

Public API changes (PublicAPI.Unshipped.txt)

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

Tests

  • QuickGridNoInteractivityTest — E2E tests for SSR (no interactivity) scenarios: paginator display, navigation, link-based pagination, disabled state, sorting by various types, multi-grid independence, dual paginators, out-of-range/invalid page handling, and direct URL loading with sort/page parameters.
  • QuickGridInteractiveTest — E2E tests for interactive server rendering: anchor-based sorting and pagination with URL query parameter verification, dual paginators syncing, and multi-grid independence with prefixed query parameters.
  • QuickGridInteractiveCompatTest — E2E tests for the opt-out path (feature flag disabled via --DisableUrlDrivenNavigation=true): verifies button-based sorting and pagination still work, and that the URL does not change during interactions.
  • GridRaceConditionTest — updated to register NavigationManager in the test service provider.
  • Test assets — new QuickGridComponent.razor (SSR page with multiple grids and paginators), QuickGridInteractive.razor (interactive server page), and QuickGridDualPaginatorComponent.razor (dual paginator test component).

Breaking change

When the feature flag is enabled (the default), sort column headers and paginator buttons change from <button> elements to <a> (link) elements. Any custom CSS targeting button.col-title or nav button in the paginator will need to be updated to target a.col-title or nav a respectively. To revert to the previous behavior, set the AppContext switch to false:

AppContext.SetSwitch("Microsoft.AspNetCore.Components.QuickGrid.EnableUrlBasedQuickGridNavigationAndSorting", false);

Additionally, when multiple QuickGrid components are used on the same page, each one now must have a unique QueryParameterNamePrefix value. Since QueryParameterNamePrefix defaults to "", having two or more grids without explicitly setting different values 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):

<QuickGrid Items="@people">...</QuickGrid>
<QuickGrid Items="@cities">...</QuickGrid>

After (requires unique QueryParameterNamePrefix per grid):

<QuickGrid Items="@people">...</QuickGrid>
<QuickGrid Items="@cities" QueryParameterNamePrefix="cities">...</QuickGrid>

Shared PaginationState limitation

Each QuickGrid propagates its page query parameter name to the associated PaginationState during OnParametersSetAsync, so that the connected Paginator uses the same query parameter name. This means multiple QuickGrid components must not share the same PaginationState instance if they have different QueryParameterNamePrefix values — the last grid to render will overwrite the query name on the shared state, causing the Paginator to read from the wrong query parameter. Each grid should have its own PaginationState instance.

Usage example

@using Microsoft.AspNetCore.Components.QuickGrid

<QuickGrid Items="@people" Pagination="@pagination">
    <PropertyColumn Property="@(p => p.Name)" Sortable="true" />
    <PropertyColumn Property="@(p => p.Age)" Sortable="true" />
    <PropertyColumn Property="@(p => p.BirthDate)" Format="yyyy-MM-dd" Sortable="true" />
</QuickGrid>
<Paginator State="@pagination" />

@* Multiple grids on the same page with different query parameter prefixes *@
<QuickGrid Items="@cities" Pagination="@pagination2" QueryParameterNamePrefix="cities">
    <PropertyColumn Property="@(c => c.Name)" Sortable="true" />
    <PropertyColumn Property="@(c => c.Country)" Sortable="true" />
</QuickGrid>
<Paginator State="@pagination2" />

@code {
    PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
    PaginationState pagination2 = new PaginationState { ItemsPerPage = 5 };
    private IQueryable<Person> people = GetPeople().AsQueryable();
    private IQueryable<City> cities = GetCities().AsQueryable();
}

Fixes #51249
Fixes #57289
Fixes #66301

@github-actions github-actions Bot added the area-blazor Includes: Blazor, Razor Components label Feb 17, 2026
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.
@dariatiurina dariatiurina force-pushed the 51249-quickgrid-ssr-sorting branch from 92bb4b5 to 1d9c7a7 Compare February 17, 2026 16:58
@dariatiurina dariatiurina self-assigned this Feb 17, 2026
@dariatiurina dariatiurina marked this pull request as ready for review February 23, 2026 13:20
@dariatiurina dariatiurina requested a review from a team as a code owner February 23, 2026 13:20
Copilot AI review requested due to automatic review settings February 23, 2026 13:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>.QueryName and URL query-string synchronization for sort state (including back/forward handling).
  • Updates Paginator and 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.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@dariatiurina
Copy link
Copy Markdown
Contributor Author

dariatiurina commented Feb 23, 2026

It is also a fix for #57289

@dotnet-policy-service dotnet-policy-service Bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Mar 2, 2026
@dariatiurina dariatiurina removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Mar 3, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 24 changed files in this pull request and generated 4 comments.

@dotnet-policy-service dotnet-policy-service Bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Mar 11, 2026
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>
@ilonatommy
Copy link
Copy Markdown
Member

ilonatommy commented Apr 16, 2026

Considered solutions

  • Session / TempData — Persist state server-side, keeping public APIs unchanged. Discarded, because it still doesn't allow URL sharing between users.
  • Query string parameters — Persist state in the URL as ?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.

I think we're missing MVC-like approach that would avoid a breaking change. We can use <form method="GET"> that puts the state in the URL - exactly like the <a href> approach does. When you submit a GET form, the browser navigates to ?page=3&sort=Name=order=asc. We use hidden inputs to create the URL. The original buttons themselves stay as <button>, we would just wrap them:

 <nav>
      <form method="get" @onsubmit:preventDefault>
         <input type="hidden" name="sort" value="Name" />
         <input type="hidden" name="order" value="asc" />
         <button class="col-title" type="submit" 
             @onclick="@(() => Grid.SortByColumnAsync(this))">
             Name
         </button>
     </form>
 </nav>

The trade-off is more DOM elements (the <form> wrappers and hidden inputs).

To avoid hitting server in interactivity mode (which would spoil everything - users don't go interactive to send their forms to server on each "sort" click) - we can set @onsubmit:preventDefault. Then:

SSR mode - No @onclick works (it's not rendered in SSR) → form submits natively via GET → browser navigates to the URL built from hidden inputs.
Interactive mode @onclick fires → handler calls NavigationManager.NavigateTo(...)preventDefault stops the form from actually submitting → no server round-trip.

I prepared a comparison of PR's tests (current implementation vs form-based) to analyse if it can be fragile in some scenarios.

1. PaginatorDisplaysCorrectItemCount

What: Render page, check "43 items", "Page 1 of 5"

  • <a href>: Works — links rendered, state is irrelevant for display
  • <form GET>: Works — buttons rendered, same display logic

Identical. This tests rendering, not interaction.

2. PaginatorGoNextShowsNextPage

What: Click "next" → page 2, URL contains page=2

  • <a href>: Click <a href="?page=2"> → browser navigates
  • <form GET>: Click <button type="submit"> inside <form> with hidden <input name="page" value="2"> → browser navigates to ?page=2

Identical result. Both produce the same URL.

3. PaginatorLinkLoadsCorrectPage

What: Navigate directly to ?page=3

  • <a href>: Component reads query string on render
  • <form GET>: Component reads query string on render

Identical. Both approaches read query params the same way on load.

4. PaginatorGoPreviousFromSecondPage

What: Next → page 2, then Previous → page 1

  • <a href>: Two link clicks, two navigations
  • <form GET>: Two form submits, two navigations

Identical.

5. PaginatorNavigationLinksDisabledCorrectly

What: First/prev disabled on page 1, next/last disabled on last page

  • <a href>: Uses aria-disabled="true", href=null, pointer-events: none
  • <form GET>: Uses native disabled attribute on <button> — simpler, better accessibility, no CSS workaround needed

<form GET> wins. Native disabled is better than the aria-disabled + pointer-events workaround.

6. PaginatorOutOfRangePageClampsToLastPage

What: ?page=999 → shows last page

  • Both: Server-side clamping logic, independent of HTML element choice

Identical.

7. PaginatorInvalidPageValueDefaultsToFirstPage

What: ?page=abc → shows page 1

  • Both: Server-side parsing logic, independent of HTML element choice

Identical.

8. CanColumnSortByInt / String / DateOnly

What: Click sort header → asc, click again → desc, verify URL and data

  • <a href>: Click <a href="?sort=PersonId&order=asc"> → navigates
  • <form GET>: Click <button type="submit"> in form with hidden inputs → submits to ?sort=PersonId&order=asc

Identical result. But with <form GET>, the form also needs hidden inputs for any existing pagination state. E.g., if you're on page 3, the sort form needs <input type="hidden" name="page" value="3"> — or intentionally omits it to reset to page 1 (which is standard UX).

9. SortLinkLoadsCorrectOrder

What: Navigate directly to ?sort=PersonId&order=desc

  • Both: Read query params on render

Identical.

10. SortNavigationWithPagination

What: On ?page=2, click sort → URL has sort params

  • <a href>: GetUriWithQueryParameters merges sort into existing URL → ?page=2&sort=PersonId&order=asc
  • <form GET>: Form has hidden inputs for page=2 (read from current URL) + sort fields → same result

Identical result. Note: preserving page=2 after sort is actually debatable UX — most grids reset to page 1 on sort change.

11. MultiplePaginatorsWorkIndependently

What: Two grids with different QueryNames, paginate grid2 → grid1 stays on page 1

  • <a href>: GetUriWithQueryParameter("QuickGrid2_page", 2) merges into URL preserving grid1's params
  • <form GET>: Grid2's paginator form submits QuickGrid2_page=2 + hidden inputs preserving grid1's page, sort, order

Identical result. <form GET> needs hidden inputs for grid1's params — the component parses current URL and emits <input type="hidden"> for all query params not matching its own prefix.

12. TwoGridsSortIndependently

What: Sort grid1 by PersonId, sort grid2 by Name → both sorts preserved in URL

  • <a href>: Each click merges its sort params into the full URL
  • <form GET>: Each form carries hidden inputs for the other grid's params

Identical result. This is the hardest case for <form GET> — but it works. Grid1's sort form emits hidden inputs for QuickGrid2_sort, QuickGrid2_order, QuickGrid2_page. Grid2's sort form emits hidden inputs for sort, order, page.

13. DualPaginatorsNavigatesAndBothUpdate

What: Two paginators on same grid, click top → both show page 2

  • <a href>: Both paginators read same URL, both update
  • <form GET>: Both paginators read same URL, both update

Identical.

@oroztocil
Copy link
Copy Markdown
Member

oroztocil commented Apr 16, 2026

The element change for sortable headers possibly fixes #66301.

@dariatiurina Could you check how do the new sortable headers (with <a> instead of <button>) look like when you try to print the page in the browser (specifically in Chrome or Edge)?

@dariatiurina dariatiurina removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Apr 17, 2026
Copy link
Copy Markdown
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Some small comments, but we can do that in a follow up if needed

Comment on lines +18 to +19
<a class="go-first" href="@(CanGoBack ? GetPageUrl(0) : null)" title="Go to first page" aria-label="Go to first page" aria-disabled="@(!CanGoBack ? "true" : "false")">«</a>
<a class="go-previous" href="@(CanGoBack ? GetPageUrl(State.CurrentPageIndex - 1) : null)" title="Go to previous page" aria-label="Go to previous page" aria-disabled="@(!CanGoBack ? "true" : "false")">‹</a>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap the button inside an a tag instead here, but we need to think if we should do this differently as I mentioned

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@javiercn Do we also want to change ColumnBase.razor to be link wrapped around button? It will return #66301 to the previous buggy behaviour.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, stale comment. We can't wrap button inside a. It's not valid HTML. Your change is the best we can do

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to use an AppCompatSwitch here

/// The parameter from which the page and sorting URL parameters are derived. The default value is an empty string, which results in query parameters named "page", "sort", and "order". If you provide a non-empty value, for example "products",
/// then the query parameters will be "products_page", "products_sort", and "products_order". This allows you to use multiple <see cref="QuickGrid{TGridItem}"/> components on the same page without their URL parameters conflicting with each other.
/// </summary>
[Parameter] public string QueryName { get; set; } = "";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This probably deserves a more descriptive name, like Query(String)?ParameterNamePrefix or something like that. It's confusing because it's a prefix, not the actual name.

<Reference Include="Microsoft.Extensions.Caching.Hybrid" />
<Reference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" />
<Reference Include="Microsoft.AspNetCore.Session" />
<Reference Include="Microsoft.AspNetCore.Components.QuickGrid" />
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this reference needed? I would imagine that this is brought in already by the appropriate test app

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, The two other files in this project that we re added should go into BasicTestApp, not TestServer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@javiercn BasicTestApp cannot test SSR. That is reason why files are in TestServer

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough

@dariatiurina
Copy link
Copy Markdown
Contributor Author

The element change for sortable headers possibly fixes #66301.

@dariatiurina Could you check how do the new sortable headers (with <a> instead of <button>) look like when you try to print the page in the browser (specifically in Chrome or Edge)?

@oroztocil I checked and the PR in the current version (links instead of buttons) fixes the issue.

@dariatiurina dariatiurina force-pushed the 51249-quickgrid-ssr-sorting branch from 2206ef5 to df8c410 Compare April 24, 2026 13:24
@dariatiurina dariatiurina requested a review from ilonatommy April 24, 2026 13:24
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

5 participants