PagingTagHelper v1.0.0: Enterprise-Ready Pagination voor Modern ASP.NET Core (Nederlands (Dutch))

PagingTagHelper v1.0.0: Enterprise-Ready Pagination voor Modern ASP.NET Core

Friday, 07 November 2025

//

21 minute read

LET OP: Binnenkort, gewoon de laatste hand aan het. Volg GitHub! .

Dit is alleen maar om jullie te laten zien wat IK BEN het boeken van vooruitgang met deze controle! Het zal het wachten waard zijn.

Inleiding

Na maanden van evolutie en waardevolle feedback van de community (5.7k+ downloads!), ben ik blij om aan te kondigen dat de PagingTagHelper bibliotheek versie 1.0.0 heeft bereikt. Dit is niet alleen een versie nummer hobbel ... het vertegenwoordigt een volledige rijping van de bibliotheek met functies die het geschikt maken voor real-world, productie toepassingen.

Als je deze serie hebt gevolgd, zul je je herinneren dat we begonnen zijn met blote botten paging, toegevoegd sorteerbare kopteksten, en gewonnen paginagrootteregeling. Versie 1.0.0 neemt alles wat we hebben geleerd en voegt kritische enterprise features:

  • Continuation Token Pagination voor NoSQL-databases (Cosmos DB, DynamoDB, Azure Table Storage)
  • Meertalige lokalisatie ondersteuning van 8 talen buiten de doos
  • Flexibele JavaScript-modi van HTMX naar zero-JavaScript
  • Puur uitzicht op achterwind zonder DaisyUI afhankelijkheden
  • Slimme URL-parameterbewaring in alle navigatie
  • HTMX 2.0.4 upgrade met achterwaartse compatibiliteit

Laten we in elk van deze functies duiken en zien hoe ze samenwerken om een echt flexibele paginatie-oplossing te creëren.

NuGet NuGet

Continuation Token Pagination

Traditionele paginatie werkt prachtig met SQL databases waar u gemakkelijk SKIP en TAKE records. Maar wat gebeurt er als je werkt met NoSQL databases zoals Cosmos DB, DynamoDB, of Azure Table Storage? Deze databases ondersteunen geen offset-based pagination in plaats daarvan, ze gebruiken continuation tokens.

Begrijpen Token-Based Pagination

Hier is hoe continuation token pagination verschilt van traditionele paging:

graph TD
    A[Traditional Paging] --> B[Page 1: OFFSET 0 LIMIT 10]
    A --> C[Page 2: OFFSET 10 LIMIT 10]
    A --> D[Page 3: OFFSET 20 LIMIT 10]

    E[Token-Based Paging] --> F[Page 1: No token]
    F --> G[Returns: Data + Token_A]
    G --> H[Page 2: Token_A]
    H --> I[Returns: Data + Token_B]
    I --> J[Page 3: Token_B]

    style A stroke:#0ea5e9,stroke-width:3px
    style E stroke:#ec4899,stroke-width:3px

Traditionele pagina's:

  • U geeft precies aan welke records u moet ophalen (OFFSET/LIMIT)
  • U kunt rechtstreeks naar elke pagina springen
  • Database moet door alle vorige records scannen

Pagina's op basis van token:

  • Database geeft een ondoorzichtig token terug dat "waar verder te gaan" voorstelt
  • Token formaat is database-specifiek en ondoorzichtig voor de client
  • Voorwaartse navigatie is natuurlijk, achteruit navigatie vereist token geschiedenis

Voortzetting Pager Implementatie

Het nieuwe <continuation-pager> tag helper maakt het implementeren van token-gebaseerde paginatie eenvoudig. Eerst, maak een model dat implementeert IContinuationPagingModel:

public class ProductPagingViewModel : IContinuationPagingModel
{
    public string? NextPageToken { get; set; }
    public bool HasMoreResults { get; set; }
    public int PageSize { get; set; } = 25;
    public int CurrentPage { get; set; } = 1;
    public Dictionary<int, string>? PageTokenHistory { get; set; }
    public ViewType ViewType { get; set; } = ViewType.TailwindAndDaisy;

    // Your actual data
    public List<Product> Products { get; set; } = new();
}

De interface is minimaal maar krachtig. Laten we eens kijken naar wat elke eigenschap doet:

  • NextPageToken: Het token om de volgende pagina op te halen (geleverd door uw database)
  • HasMoreResults: Booleaans geeft aan of er meer pagina's zijn
  • PageSize: Items per pagina
  • CurrentPage: Alleen weergeven paginanummer voor UI
  • PageTokenHistory: Dictionary mapping pagina nummers to tokens voor achteruit navigatie
  • ViewType: Welk CSS-kader te gebruiken voor rendering

Laten we nu een controller actie implementeren die Cosmos DB-stijl paginatie simuleert:

[Route("Products")]
public async Task<IActionResult> Products(
    int currentPage = 1,
    int pageSize = 25,
    string? pageToken = null,
    string? tokenHistory = null)
{
    // Simulate fetching from Cosmos DB
    var cosmosResults = await _cosmosService.GetProductsAsync(
        pageSize: pageSize,
        continuationToken: pageToken
    );

    // Deserialize token history for backward navigation
    var history = string.IsNullOrEmpty(tokenHistory)
        ? new Dictionary<int, string>()
        : JsonSerializer.Deserialize<Dictionary<int, string>>(tokenHistory)
          ?? new Dictionary<int, string>();

    // Store current token in history
    if (!string.IsNullOrEmpty(pageToken))
    {
        history[currentPage] = pageToken;
    }

    var viewModel = new ProductPagingViewModel
    {
        CurrentPage = currentPage,
        PageSize = pageSize,
        NextPageToken = cosmosResults.ContinuationToken,
        HasMoreResults = cosmosResults.HasMoreResults,
        PageTokenHistory = history,
        Products = cosmosResults.Items
    };

    if (Request.IsHtmx())
    {
        return PartialView("_ProductList", viewModel);
    }

    return View(viewModel);
}

Deze implementatie laat zien hoe tokengeschiedenis backward navigation mogelijk maakt. Zonder dit zou continuation token pagination alleen "Next" knoppen ondersteunen. Door een woordenboek van pagina-to-to-token mappings te behouden, kunnen we zowel "Vorige" als "Next" navigatie ondersteunen.

Hier is de stroom van token accumulatie gevisualiseerd:

sequenceDiagram
    participant User
    participant Controller
    participant Database
    participant TokenHistory

    User->>Controller: Request Page 1 (no token)
    Controller->>Database: Query with no token
    Database-->>Controller: Data + Token_A
    Controller->>TokenHistory: Store Token_A for page 1
    Controller-->>User: Display Page 1

    User->>Controller: Request Page 2 (Token_A)
    Controller->>Database: Query with Token_A
    Database-->>Controller: Data + Token_B
    Controller->>TokenHistory: Add Token_B for page 2
    Controller-->>User: Display Page 2

    User->>Controller: Request Page 1 (retrieve from history)
    Controller->>TokenHistory: Get Token for Page 1
    Controller->>Database: Query with Token_A
    Database-->>Controller: Data + Token_A
    Controller-->>User: Display Page 1

In uw Razor-weergave is het gebruik van de continuation pager eenvoudig:

@model ProductPagingViewModel

<div id="product-container">
    <table class="table">
        <thead>
            <tr>
                <th>Product</th>
                <th>Company</th>
                <th>Price</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in Model.Products)
            {
                <tr>
                    <td>@product.Name</td>
                    <td>@product.CompanyName</td>
                    <td>[email protected]("N2")</td>
                </tr>
            }
        </tbody>
    </table>

    <continuation-pager
        model="Model"
        htmx-target="#product-container"
        show-page-number="true"
        show-pagesize="true" />
</div>

De tag-helper automatisch:

  • Serialiseert de tokengeschiedenis in queryparameters
  • Bouwt navigatie-URL's met de juiste tokens
  • Schakelt "Vorig" uit wanneer op pagina 1
  • Schakelt "Volgende" uit wanneer HasMoreResults is onjuist
  • Bewaart alle andere zoekparameters (zoeken, filters, enz.)

Token History for Backward Navigation

Het geniale van de token geschiedenis benadering is dat het is volledig optioneel. Als je alleen "Next" navigatie (oneindige scroll, bijvoorbeeld), kunt u token geschiedenis volledig negeren:

<continuation-pager
    model="Model"
    enable-token-accumulation="false"
    show-page-number="false" />

Dit maakt gewoon een "Next" knop zonder pagina-indicatoren of geschiedenisbeheer.

Voor volledige navigatie wordt de tokengeschiedenis automatisch geserialiseerd als JSON in de query string. Hier is hoe een URL eruit ziet met tokengeschiedenis:

/Products?currentPage=3&pageSize=25&pageToken=abc123&tokenHistory=%7B%221%22%3A%22xyz789%22%2C%222%22%3A%22abc123%22%7D

De tokenHistory parameter bevat het gecodeerde woordenboek, waardoor achteruit navigatie naadloos.

Genummerde paginanavigatie

Een van de belangrijkste UX verbeteringen in de continuation pager is genummerde paginaknoppen. Als u navigeert naar voren, de pager toont klikbare paginanummers voor alle bezochte pagina's:

Initial page 1:    [Next →]
After next click:  [← Prev] [1] [2 active] [3 disabled] [Next →]
After next click:  [← Prev] [1] [2] [3 active] [4 disabled] [Next →]
Click page 2:      [← Prev] [1] [2 active] [3] [4 disabled] (no next - not visited yet)

Dit biedt traditionele pagination UX terwijl het onderhouden van token-gebaseerde backend architectuur. De implementatie slaat tokens voor elke bezochte pagina, waardoor directe navigatie naar een eerder bezochte pagina.

Beperkende groei van de geschiedenis:

Om ongebonden geheugengebruik te voorkomen, ingesteld max-history-pages (standaard: 20):

<continuation-pager
    model="Model"
    max-history-pages="50"
    show-page-number="true" />

Wanneer de limiet is bereikt, worden de oudste pagina tokens automatisch gesnoeid.

Critical: Query Parameter conservation

Dit is het belangrijkste kenmerk van de continuation pager implementatie.

Continuation tokens zijn alleen geldig met dezelfde query context (filters, soorten, zoekopdrachten) die ze gegenereerd hebben. Het gebruik van een token met verschillende query parameters zal onjuiste gegevens teruggeven of volledig falen.

De vervolg pager behoudt automatisch ALLE zoekparameters behalve de eigen:

<!-- URL with filters -->
/Products?category=electronics&brand=acme&minPrice=100

<!-- After clicking Next -->
/Products?category=electronics&brand=acme&minPrice=100&currentPage=2&pageToken=xyz123&tokenHistory={...}

<!-- All filters preserved! Token is valid because query context matches. -->

U kunt dit gedrag uitschakelen indien nodig:

<continuation-pager
    model="Model"
    preserve-query-parameters="false" />

Maar dit is sterk ontmoedigd Tenzij je absoluut zeker bent dat je tokens niet afhankelijk zijn van query context.

Waarom dit belangrijk is:

Cosmos DB voorbeeld:

// Page 1 with filter
var query = container.GetItemQueryIterator<Product>(
    "SELECT * FROM c WHERE c.category = 'electronics'",
    continuationToken: null
);
var response = await query.ReadNextAsync();
// Returns: Products + Token_A

// Page 2 with SAME filter - Token_A is valid
var query2 = container.GetItemQueryIterator<Product>(
    "SELECT * FROM c WHERE c.category = 'electronics'",
    continuationToken: Token_A  // ✅ Works!
);

// Page 2 with DIFFERENT filter - Token_A is invalid
var query3 = container.GetItemQueryIterator<Product>(
    "SELECT * FROM c WHERE c.category = 'computers'",
    continuationToken: Token_A  // ❌ Wrong results or error!
);

De automatische parameter bewaring van de vervolgpager zorgt ervoor dat tokens altijd worden gebruikt met hun oorspronkelijke query-context.


Ondersteuning voor lokalisatie

Moderne toepassingen dienen wereldwijd publiek, en paginatie controles moeten de taal van uw gebruikers spreken. Versie 1.0.0 omvat uitgebreide lokalisatie ondersteuning ingebouwd in de bibliotheek.

Ingebouwde talen

De bibliotheek verscheept met vertalingen voor 8 talen:

  • Code - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal - taal |------|----------| | en Engels (onvertaald) | de Duits (Deutsch) | es Spaans (Español) | fr Frans (Français) | it Italiaans (Italiano) | pt Portugees (Português) | ja Japans ( . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . | zh-Hans Vereenvoudigd Chinees ( . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Alle tekst is gelokaliseerd, inclusief:

  • Vorige/Volgende/Eerste/Laatste knoopetiketten
  • Samenvatting van pagina's ("Toon X tot Y van Z-items")
  • ARIA-labels voor toegankelijkheid
  • Paginagrootte label ("Items per pagina")

Het lokalisatiesysteem wordt aangedreven door .resx resource-bestanden, waardoor het gemakkelijk is om uw eigen talen toe te voegen. Alle resource-bestanden zijn in mostlylucid.pagingtaghelper/Resources/.

Lokalisatie Gebruik

Het gebruik van localisatie is eenvoudig. language attribuut:

<paging
    model="Model"
    language="de"
    show-summary="true"
    first-last-navigation="true" />

Hiermee wordt alle tekst in het Duits weergegeven:

<!-- Previous button -->
<button>‹ Vorherige</button>

<!-- Summary -->
<div class="text-sm text-gray-600">
    Zeige 1 bis 10 von 256 Einträgen
</div>

<!-- Next button -->
<button>Nächste ›</button>

Voor dynamische taalschakeling op basis van gebruikersvoorkeuren, stelt u de taal in uw controller in:

public async Task<IActionResult> Products(
    int page = 1,
    int pageSize = 10,
    string language = "en")
{
    var pagingModel = await GenerateModel(page, pageSize);
    ViewBag.SelectedLanguage = language;
    return View(pagingModel);
}

Maak dan in uw ogen een taalkiezer aan:

@{
    var selectedLanguage = ViewBag.SelectedLanguage as string ?? "en";
    var languages = new Dictionary<string, string>
    {
        { "en", "English" },
        { "de", "German" },
        { "es", "Spanish" },
        { "fr", "French" },
        { "it", "Italian" },
        { "pt", "Portuguese" },
        { "ja", "Japanese" },
        { "zh-Hans", "Chinese" }
    };
}

<select onchange="window.location.href='/Products?language=' + this.value">
    @foreach (var lang in languages)
    {
        <option value="@lang.Key" selected="@(lang.Key == selectedLanguage)">
            @lang.Value
        </option>
    }
</select>

<paging
    model="Model"
    language="@selectedLanguage"
    link-url="/Products" />

U kunt ook individuele tekststrings overschrijven terwijl u nog steeds profiteert van localisatie voor andere elementen:

<paging
    model="Model"
    language="ja"
    previous-page-text="戻る"
    next-page-text="次へ"
    summary-template="全{TotalItems}件中 {StartItem}~{EndItem}件を表示" />

De PagingLocalizer service verwerkt cultuur-specifieke opmaak automatisch. Als u een ongeldige taalcode doorgeeft, valt het sierlijk terug naar het Engels.

Voor HTMX integratie, wilt u de taal te behouden in alle verzoeken:

<script>
    htmx.on('htmx:configRequest', function(event) {
        if (event.detail.path.includes('/Products')) {
            event.detail.parameters.language = '@selectedLanguage';
        }
    });
</script>

Dit zorgt ervoor dat HTMX partial view updates de geselecteerde taal behouden.


JavaScript-modi

Een van de belangrijkste verbeteringen in v1.0.0 is de invoering van flexibele JavaScript modi. Eerder had je een booleaanse keuze: use-htmx="true" of use-htmx="false". Nu heb je vijf verschillende modi, elk geoptimaliseerd voor verschillende scenario's.

Beschikbare modi

Hier is de volledige indeling van JavaScript modi:

graph TD
    A[JavaScript Modes] --> B[HTMX]
    A --> C[HTMXWithAlpine]
    A --> D[Alpine]
    A --> E[PlainJS]
    A --> F[NoJS]

    B --> B1[Uses HTMX for partial updates]
    B --> B2[hx-get, hx-target, hx-swap]

    C --> C1[HTMX + Alpine.js directives]
    C --> C2[Enhanced interactivity]

    D --> D1[Pure Alpine.js]
    D --> D2[x-data, @click handlers]

    E --> E1[Vanilla JavaScript]
    E --> E2[onclick handlers]

    F --> F1[Zero JavaScript]
    F --> F2[Standard anchor links & forms]

Laten we elke modus in actie zien:

1. HTMX-modus (standaard)

<paging
    model="Model"
    js-mode="HTMX"
    htmx-target="#results-container" />

Renders:

<button hx-get="/Products?page=2" hx-target="#results-container" hx-swap="outerHTML">
    Next ›
</button>

Perfect voor dynamische pagina-updates zonder volledige pagina-herladen. Dit is de aanbevolen modus voor moderne ASP.NET Core toepassingen.

2. HTMXMetAlpine-modus

<paging
    model="Model"
    js-mode="HTMXWithAlpine"
    htmx-target="#results-container" />

Renders:

<button
    x-data
    hx-get="/Products?page=2"
    hx-target="#results-container"
    hx-swap="outerHTML">
    Next ›
</button>

Combineert HTMX voor navigatie met Alpine.js voor extra client-side interactiviteit. Gebruik dit wanneer je reactieve UI-elementen nodig hebt naast paginatie (loading-indicatoren, animaties, client-side validatie).

3. Alpenmodus

<paging
    model="Model"
    js-mode="Alpine" />

Renders:

<button
    x-data
    @click="window.location.href = '/Products?page=2'">
    Next ›
</button>

Pure Alpine.js zonder HTMX. Handig als je al Alpine.js gebruikt maar geen HTMX afhankelijkheden wilt.

4. PlainJS-modus

<paging
    model="Model"
    js-mode="PlainJS" />

Renders:

<button onclick="window.location.href = '/Products?page=2'">
    Next ›
</button>

Geen kaderafhankelijkheden, alleen vanille JavaScript. Deze modus bevat ook een helper voor paginagroottewijzigingen:

@Html.PageSizeOnchangeSnippet()

Dit injecteert de nodige JavaScript voor het verwerken van paginagrootte dropdown veranderingen zonder HTMX.

5. NoJS-modus

<paging
    model="Model"
    js-mode="NoJS" />

Renders:

<!-- Navigation uses standard anchor links -->
<a href="/Products?page=2">Next ›</a>

<!-- Page size uses a form with submit button -->
<form method="get" action="/Products">
    <input type="hidden" name="page" value="1" />
    <select name="pageSize" onchange="this.form.submit()">
        <option value="10">10</option>
        <option value="25" selected>25</option>
        <option value="50">50</option>
    </select>
    <noscript>
        <button type="submit">Update</button>
    </noscript>
</form>

Zero JavaScript vereist. Perfect voor:

  • Toegankelijkheidseisen
  • Progressieve verbeteringsscenario's
  • Omgevingen waar JavaScript is uitgeschakeld
  • SEO-kritische pagina's waar u rupsvriendelijke navigatie wilt

De schoonheid van dit systeem is dat alle modi uw bestaande query parameters behouden. Of u nu filtert op categorie, zoeken of sorteren, de paginatie behoudt automatisch uw status.

Migratie uit use-htmx

Voor achterwaartse compatibiliteit, de oude use-htmx attribuut werkt nog steeds:

<!-- Old syntax (still works) -->
<paging model="Model" use-htmx="true" />
<!-- Equivalent to js-mode="HTMX" -->

<paging model="Model" use-htmx="false" />
<!-- Equivalent to js-mode="PlainJS" -->

Ik adviseer echter om te migreren naar de nieuwe js-mode attribuut voor duidelijkheid:

<!-- New syntax (recommended) -->
<paging model="Model" js-mode="HTMX" />
<paging model="Model" js-mode="PlainJS" />

WeergaveType verbeteringen

Versie 1.0.0 introduceert twee belangrijke ViewType toevoegingen die betrekking hebben op gemeenschappelijke real-world scenario's.

Pure staartwind

Eerder, als je wilde TailwindCSS styling, je kreeg de TailwindAndDaisy weergave die DaisyUI componenten gebruikt. Dit is geweldig als je DaisyUI al gebruikt, maar wat als je pure Tailwind wilt zonder de DaisyUI afhankelijkheid?

Enter ViewType.Tailwind:

<paging
    model="Model"
    view-type="Tailwind" />

Dit maakt alleen gebruik van standaard Tailwind utility classes:

<div class="flex gap-2 items-center">
    <button class="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700">
        ‹ Previous
    </button>

    <div class="px-3 py-1 text-sm font-medium bg-gray-100 dark:bg-gray-700 dark:text-white rounded-md">
        Page 1
    </div>

    <button class="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700">
        Next ›
    </button>
</div>

Nee btn, badge, of join Klassen, gewoon pure Tailwind. Dit geeft u volledige controle over styling zonder onderdelenbibliotheek afhankelijkheden.

Vergelijking:

WeergaveType CSS Framework Onderdelenbibliotheek Gebruik Case |----------|---------------|-------------------|----------| | TailwindAndDaisy TailwindCSS DaisyUI Projecten die al DaisyUI gebruiken | Tailwind Puur Tailwind CSS Geen Puur Tailwind projecten | Bootstrap Bootstrap Bootstrap componenten Bootstrap projecten | Plain Ingebedde CSS Geen Geen Framework afhankelijkheden | NoJS Ingebedde CSS GeenZero JavaScript vereisten

NoJS-modus

De NoJS ViewType combineert nul JavaScript met de Plain CSS styling:

<paging
    model="Model"
    view-type="NoJS"
    show-pagesize="true" />

Belangrijkste verschillen met andere weergavetypen:

  1. Navigatie maakt gebruik van ankerverbindingen, geen knoppen:
<a href="/Products?page=2" class="pager-button">Next ›</a>
  1. Paginagrootteselectie is een formulier:
<form method="get" action="/Products" class="page-size-form">
    <!-- Preserves all current query parameters as hidden inputs -->
    <input type="hidden" name="search" value="laptop" />
    <input type="hidden" name="category" value="electronics" />

    <!-- Reset to page 1 when changing page size -->
    <input type="hidden" name="page" value="1" />

    <label for="pageSize">Items per page:</label>
    <select name="pageSize" onchange="this.form.submit()">
        <option value="10">10</option>
        <option value="25" selected>25</option>
        <option value="50">50</option>
    </select>

    <!-- Button visible when JavaScript is disabled -->
    <noscript>
        <button type="submit" class="page-size-button">Update</button>
    </noscript>
</form>

De onchange="this.form.submit()" biedt gemak wanneer JavaScript beschikbaar is, maar de <noscript> knop zorgt voor volledige functionaliteit als het niet zo is.


URL-parameterbewaring

Een van de meest frustrerende aspecten van paginatie implementaties is het verliezen van uw filters, zoektermen, of sorteren orde bij het navigeren tussen pagina's. Versie 1.0.0 lost dit elegant door automatisch alle queryparameters behouden, behalve de eigen parameters van de paginatiecontrole.

Deze functie werkt identiek over zowel reguliere piepers als vervolgpiepers, en over alle JavaScript-modi en ViewTypes.

Zo werkt het intern:

string BuildQueryString(string? token, int page)
{
    var query = new Dictionary<string, string>();

    // Define continuation pager's own parameters that should be excluded from preservation
    var pagerParams = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "pageSize", "currentPage", "pageToken", "tokenHistory"
    };

    // Add parameter prefix variants if using prefixed parameters
    if (!string.IsNullOrEmpty(Model.ParameterPrefix))
    {
        pagerParams.Add($"{Model.ParameterPrefix}_pageSize");
        pagerParams.Add($"{Model.ParameterPrefix}_currentPage");
        pagerParams.Add($"{Model.ParameterPrefix}_pageToken");
        pagerParams.Add($"{Model.ParameterPrefix}_tokenHistory");
    }

    // Preserve all existing query parameters (except pager's own) if enabled
    if (Model.PreserveQueryParameters)
    {
        foreach (var param in ViewContext.HttpContext.Request.Query)
        {
            if (!pagerParams.Contains(param.Key))
            {
                query[param.Key] = param.Value.ToString();
            }
        }
    }

    // Add continuation pager parameters (with prefix if specified)
    var pageSizeParam = Model.GetParameterName("pageSize");
    var currentPageParam = Model.GetParameterName("currentPage");
    var pageTokenParam = Model.GetParameterName("pageToken");
    var tokenHistoryParam = Model.GetParameterName("tokenHistory");

    query[pageSizeParam] = pageSize.ToString();
    query[currentPageParam] = page.ToString();

    if (!string.IsNullOrEmpty(token))
        query[pageTokenParam] = token;

    if (Model.EnableTokenAccumulation)
        query[tokenHistoryParam] = tokenHistoryJson;

    return string.Join("&", query.Select(kvp =>
        $"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}"));
}

Deze aanpak houdt het volgende in:

Scenario 1: Zoeken + Paginatie

Initial URL: /Products?search=laptop&category=electronics&page=1
Click Next: /Products?search=laptop&category=electronics&page=2
Change Page Size: /Products?search=laptop&category=electronics&page=1&pageSize=50

Scenario 2: Sorteren + Paginatie

Initial URL: /Products?orderBy=price&descending=true&page=1
Click Page 3: /Products?orderBy=price&descending=true&page=3

Scenario 3: Continuation Pager met filters

Initial URL: /Products?category=electronics&brand=acme
Click Next: /Products?category=electronics&brand=acme&currentPage=2&pageToken=abc123&tokenHistory={...}

Dezelfde bewaring werkt in vormen (NoJS-modus). Bij het renderen van het paginagrootteformulier bevat het scherm automatisch verborgen ingangen voor alle parameters voor niet-paginatie:

<form method="get" action="@linkUrl" class="page-size-form">
    @* Preserve all existing query parameters except pageSize and page-related ones *@
    @foreach (var param in ViewContext.HttpContext.Request.Query)
    {
        if (!new[] { "pageSize", "currentPage", "pageToken", "tokenHistory" }
            .Contains(param.Key, StringComparer.OrdinalIgnoreCase))
        {
            <input type="hidden" name="@param.Key" value="@param.Value" />
        }
    }

    @* Reset to page 1 when changing page size *@
    <input type="hidden" name="currentPage" value="1" />

    <select name="pageSize" onchange="this.form.submit()">
        <!-- options -->
    </select>
</form>

Dit werkt naadloos over alle JavaScript-modi en alle ViewTypes. U hoeft nooit handmatig query string propagation te beheren.


Migratiegids

Upgraden van pre-1.0 versies is eenvoudig, maar er zijn een paar brekende veranderingen om bewust van te zijn.

Veranderingen breken

1. use-htmx is verouderd (maar werkt nog)

Oud:

<paging model="Model" use-htmx="true" />
<paging model="Model" use-htmx="false" />

Nieuw (aanbevolen):

<paging model="Model" js-mode="HTMX" />
<paging model="Model" js-mode="PlainJS" />

2. ViewType.TailwindAndDaisy maakt nu gebruik van volledige DaisyUI componenten

Als u één van de volgende geneesmiddelen gebruikt: ViewType.TailwindAndDaisy en wil pure Tailwind zonder DaisyUI:

Oud gedrag (zuivere staartwind):

<paging model="Model" view-type="TailwindAndDaisy" />

Nieuw (om oud gedrag te krijgen):

<paging model="Model" view-type="Tailwind" />

Blijf gebruiken TailwindAndDaisy als u DaisyUI-componenten gebruikt:

<paging model="Model" view-type="TailwindAndDaisy" />
<!-- Uses btn, join, badge, select, etc. -->

3. HTMX geüpgrade naar 2.0.4

Als u gebruik maakt van HTMX elders in uw toepassing, zorg ervoor dat compatibiliteit met HTMX 2.0.4. De meeste HTMX 1.x code werkt ongewijzigd, maar bekijk de HTMX 2.0 migratiegids voor edge cases.

Stapsgewijze migratie

Stap 1: Update NuGet Pakket

dotnet add package mostlylucid.pagingtaghelper --version 1.0.0

Stap 2: Bekijk uw bestaande code

Zoek uw codebase naar use-htmx attributen:

# PowerShell
Get-ChildItem -Recurse -Include *.cshtml | Select-String "use-htmx"

# Bash/Git Bash
grep -r "use-htmx" --include="*.cshtml" .

Stap 3: Bijwerken naar js-modus (aanbevolen)

Vervangen use-htmx met js-mode:

- <paging model="Model" use-htmx="true" htmx-target="#results" />
+ <paging model="Model" js-mode="HTMX" htmx-target="#results" />

- <paging model="Model" use-htmx="false" />
+ <paging model="Model" js-mode="PlainJS" />

Stap 4: Review TailwindAndDaisy gebruik

Als je DaisyUI niet geïnstalleerd hebt maar gebruikt TailwindAndDaisy:

- <paging model="Model" view-type="TailwindAndDaisy" />
+ <paging model="Model" view-type="Tailwind" />

Stap 5: Test grondig

Voer uw toepassing en test:

  • Paginanavigatie
  • Paginagroottes wijzigen
  • HTMX gedeeltelijke updates (indien HTMX wordt gebruikt)
  • Filter/zoekbehoud
  • Mobiele respons

Nieuwe functies om aan te nemen

Eenmaal gemigreerd, overwegen deze nieuwe kenmerken aan te nemen:

Lokalisatie:

<paging
    model="Model"
    language="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName" />

Continuation Pager (als u NoSQL gebruikt):

<continuation-pager
    model="Model"
    htmx-target="#results-container"
    show-page-number="true" />

NoJS-modus (voor toegankelijkheid):

<paging model="Model" js-mode="NoJS" />

Demo-toepassing

De bibliotheek bevat een uitgebreide demo applicatie met alle functies. U kunt het lokaal uitvoeren of bekijken op de demo site (binnenkort).

Het uitvoeren van de Demo Lokaal:

git clone https://github.com/scottgal/mostlylucid.pagingtaghelper.git
cd mostlylucid.pagingtaghelper/mostlylucid.pagingtaghelper.sample
dotnet run

Navigeren naar https://localhost:5001 om te onderzoeken:

  1. Basis Paginatie met Model - Traditionele paging met SQL-stijl paginatie
  2. HTMX-integratie - Dynamische pagina updates zonder volledige pagina herladen
  3. Zoeken met HTMX - Gecombineerd zoeken en pagineren
  4. Gewone CSS - Geen kader afhankelijkheden
  5. Pure staartwind - TailwindCSS zonder DaisyUI
  6. Geen JavaScript - Volledig functionele nul JS-paginatie
  7. JavaScript-modi - Alle vijf JS modes demonstreerden zij-aan-zij
  8. Pagina sorteren - Sorteerbare headers met HTMX
  9. Pagina Sorteer geen HTMX - Sorteerbare headers met volledige paginaladingen
  10. Paginagrootte met HTMX - Dynamische paginagrootte verandert
  11. Paginagrootte Geen HTMX - Paginagrootte met formulierindiening
  12. Voortzettingspager - NoSQL-stijl token-gebaseerde paginatie
  13. Lokalisatie - Taalkeuze met 8 talen

Elke demo bevat:

  • Werkbroncode
  • Verklaring van de techniek
  • Link naar de GitHub-implementatie
  • Interactieve controles om te experimenteren

Conclusie

Versie 1.0.0 is een belangrijke mijlpaal voor de PagingTagHelper bibliotheek. Wat begon als een eenvoudige werkbehoefte is geëvolueerd tot een uitgebreide, productie-ready paginatie oplossing die handvatten:

  • Traditionele SQL-paginatie met offset/limit
  • NoSQL continuation token-paginatie voor Cosmos DB, DynamoDB, enz.
  • Meertalige lokalisatie voor het wereldwijde publiek
  • Flexibele JavaScript-modi van HTMX naar zero-JavaScript
  • Meerdere CSS-kaders van DaisyUI tot pure staartwind tot geen
  • Slimme parameter bewaring in alle navigatie
  • Volledige toegankelijkheidsondersteuning met ARIA-labels en toetsenbordnavigatie

De bibliotheek is getest met 1.7k+ downloads en is klaar voor productie. Alle 106 unit tests slagen, en de uitgebreide demo applicatie toont gebruikspatronen in de echte wereld.

Wat is het volgende?

Toekomstige verbeteringen die ik overweeg:

  • Aanvullende CSS-kaderondersteuning (Materiaal UI, Bulma)
  • Meer lokalisatietalen (community contributions welcome!)
  • Server-side Blazor-componenten
  • Verbeterde toegankelijkheid voor grote datasets

Aan de slag

Installeren via NuGet:

dotnet add package mostlylucid.pagingtaghelper --version 1.0.0

Bekijk de documentatie:

Vragen, feedback of bijdragen? Open een probleem op GitHub of contacteer op Twitter @scottgal.

Gelukkige pagineren!

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.