PagingTagHelper v1.0.0: Enterprise-Ready Pagination für modernen ASP.NET Core (Deutsch (German))

PagingTagHelper v1.0.0: Enterprise-Ready Pagination für modernen ASP.NET Core

Friday, 07 November 2025

//

21 minute read

HINWEIS: KOMING SOON, nur den letzten Schliff zu setzen. Folgen Sie dem GitHub! .

Das ist nur, um Ihnen zu zeigen, dass ich mit dieser Kontrolle Fortschritte mache! Es wird das Warten wert sein.

Einleitung

Nach monatelanger Entwicklung und wertvollem Feedback aus der Community (5.7k+ Downloads!) freue ich mich, bekannt zu geben, dass die PagingTagHelper-Bibliothek die Version 1.0.0 erreicht hat. Dies ist nicht nur eine Versionsnummernumerierung – sie stellt eine vollständige Reifung der Bibliothek mit Funktionen dar, die sie für reale Produktionsanwendungen geeignet machen.

Wenn Sie diese Serie verfolgt haben, werden Sie sich erinnern, dass wir mit Bare-Knochen-Paging, hinzugefügt sortierbare Kopfzeilen, und extrahiert Seitengrößenregler. Version 1.0.0 nimmt alles, was wir gelernt haben, und fügt kritische Unternehmensfunktionen hinzu:

  • Fortsetzung Token Pagination für NoSQL-Datenbanken (Cosmos DB, DynamoDB, Azure Table Storage)
  • Mehrsprachige Lokalisierung Unterstützung von 8 Sprachen aus dem Kasten
  • Flexible JavaScript-Modi von HTMX zum Null-JavaScript
  • Reine Rückenwind-Ansichten ohne DaisyUI Abhängigkeiten
  • Intelligente URL-Parameter-Reservierung über die gesamte Navigation
  • HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,0,4 HTMX 2.0,4 HTMX 2.0,4 HTMX 2.0,4 Upgrade mit Rückwärtskompatibilität

Lassen Sie uns in jede dieser Funktionen eintauchen und sehen, wie sie zusammenarbeiten, um eine wirklich flexible Paginationslösung zu schaffen.

NuGet NuGet

Fortsetzung Token-Pagination

Traditionelle Pagination funktioniert wunderbar mit SQL-Datenbanken, wo Sie leicht SKIP und TAKE Datensätze. Aber was passiert, wenn Sie mit NoSQL-Datenbanken wie Cosmos DB, DynamoDB oder Azure Table Storage arbeiten? Diese Datenbanken unterstützen keine Offset-basierte Paginierung – stattdessen verwenden sie Fortsetzungs-Token.

Token-Based Pagination verstehen

Hier ist, wie Fortsetzung Token Pagination unterscheidet sich von traditionellen Pagination:

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

Traditionelles Paging:

  • Sie geben genau an, welche Datensätze abgerufen werden sollen (OFFSET/LIMIT)
  • Sie können zu jeder Seite direkt springen
  • Datenbank muss durch alle vorherigen Datensätze scannen

Token-Based Paging:

  • Datenbank gibt ein undurchsichtiges Token zurück, das "wo weitergeht"
  • Token-Format ist datenbankspezifisch und für den Client undurchsichtig
  • Vorwärtsnavigation ist natürlich, Rückwärtsnavigation erfordert Token Geschichte

Continuation Pager Implementation

Das neue <continuation-pager> tag helper macht die Umsetzung token-basierte pagination einfach. Zuerst, erstellen Sie ein Modell, das implementiert 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();
}

Die Schnittstelle ist minimal, aber leistungsfähig. Schauen wir uns an, was jede Eigenschaft tut:

  • NextPageToken: Das Token zum Abrufen der nächsten Seite (von Ihrer Datenbank bereitgestellt)
  • HasMoreResults: Boolean zeigt an, ob es mehr Seiten gibt
  • PageSize: Artikel pro Seite
  • CurrentPage: Anzeige-nur Seitennummer für UI
  • PageTokenHistory: Wörterbuch-Mapping-Seitennummern zu Tokens für die Rückwärtsnavigation
  • ViewType: Welche CSS-Frameworks für das Rendering zu verwenden sind

Nun lassen Sie uns eine Controller-Aktion implementieren, die Cosmos DB-Stil Pagination simuliert:

[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);
}

Diese Implementierung zeigt, wie Token-History eine Rückwärtsnavigation ermöglicht. Ohne sie würde die Fortsetzungs-Token-Pagination nur "Next"-Buttons unterstützen. Durch die Pflege eines Wörterbuchs von Seiten-zu-Token-Mappings können wir sowohl die "Vorherige" als auch die "Next"-Navigation unterstützen.

Hier ist der Fluss von Token Akkumulation visualisiert:

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 Ihrer Razor-Ansicht ist die Verwendung des Continuation Pagers einfach:

@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>

Der Tag-Helfer automatisch:

  • Serialisiert die Token-Historie in Abfrageparameter
  • Erstellt Navigations-URLs mit entsprechenden Tokens
  • Deaktiviert "Vorherig" wenn auf Seite 1
  • Deaktiviert "Weiter" wenn HasMoreResults ist falsch
  • Bewahrt alle anderen Abfrageparameter (Suche, Filter, etc.)

Token History for Backward Navigation

Das Genie des Token-History-Ansatzes ist, dass es völlig optional ist. Wenn Sie nur "Next" Navigation (unendliche Scroll, zum Beispiel) benötigen, können Sie Token-History vollständig ignorieren:

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

Dies macht nur einen "Next"-Button ohne Seitenanzeige oder Historienverwaltung.

Für die volle Navigation wird die Token-History automatisch als JSON im Query-String serialisiert. So sieht eine URL mit Token-History aus:

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

Das tokenHistory Parameter enthält das kodierte Wörterbuch und macht die Rückwärtsnavigation nahtlos.

Nummerierte Seitennavigation

Eine der wichtigsten UX-Verbesserungen im Continuation Pager ist nummerierte Seitentasten. Während Sie vorwärts navigieren, zeigt der Pager anklickbare Seitennummern für alle besuchten Seiten an:

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)

Dies bietet traditionelle Pagination UX unter Beibehaltung Token-basierte Backend-Architektur. Die Implementierung speichert Token für jede besuchte Seite, so dass direkte Navigation auf jede zuvor aufgerufene Seite.

Das Wachstum in der Geschichte begrenzen:

Um eine uneingeschränkte Speichernutzung zu verhindern, setzen Sie max-history-pages (Standard: 20):

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

Wenn das Limit erreicht ist, werden die ältesten Seiten-Token automatisch getrimmt.

Kritisch: Abfrageparameter Erhaltung

Dies ist das wichtigste Merkmal der Continuation Pager-Implementierung.

Kontinuationstoken sind nur mit demselben Abfragekontext gültig (Filter, Arten, Suchen), der sie erzeugt hat. Mit einem Token mit unterschiedlichen Abfrageparametern werden falsche Daten zurückgegeben oder komplett fehlgeschlagen.

Der Continuation Pager bewahrt automatisch ALLE Abfrageparameter außer seinen eigenen:

<!-- 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. -->

Sie können dieses Verhalten bei Bedarf deaktivieren:

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

Aber das ist stark entmutigt Es sei denn, Sie sind absolut sicher, dass Ihre Token nicht vom Abfragekontext abhängen.

Warum das so wichtig ist:

Beispiel der Kosmos-DB:

// 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!
);

Die automatische Parameterkonservierung des Continuation Pagers sorgt dafür, dass Token immer mit ihrem ursprünglichen Abfragekontext verwendet werden.


Lokalisierungsunterstützung

Moderne Anwendungen dienen globalen Zielgruppen, und Paginationssteuerungen müssen die Sprache Ihrer Benutzer sprechen. Version 1.0.0 enthält umfassende Lokalisierungsunterstützung direkt in die Bibliothek integriert.

Eingebaute Sprachen

Die Bibliothek wird mit Übersetzungen für 8 Sprachen ausgeliefert:

Sprache |------|----------| | en Englisch (Standard) | de Deutsch (Deutsch) | es Spanisch (Español) | fr Französisch (Français) | it Italienisch (Italiano) | pt Portugiesisch (Português) | ja Japanisch (日本語) | zh-Hans Chinesisch Vereinfacht ( ‐ 中文)

Der gesamte Text ist lokalisiert, einschließlich:

  • Vorherige/Nächste/Erste/Letzte Schaltflächenbeschriftungen
  • Zusammenfassung der Seite ("Zeige X zu Y von Z-Elementen")
  • ARIA-Etiketten für die Zugänglichkeit
  • Bezeichnung der Seitengröße ("Artikel pro Seite")

Das Lokalisierungssystem wird von .resx Ressourcen-Dateien, so dass es einfach, Ihre eigenen Sprachen. Alle Ressourcen-Dateien sind in mostlylucid.pagingtaghelper/Resources/.

Lokalisierung Verwendung

Lokalisierung ist einfach. Fügen Sie einfach die language Attribut:

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

Damit wird der gesamte Text in deutscher Sprache wiedergegeben:

<!-- 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>

Stellen Sie für dynamische Sprachumschaltungen basierend auf Benutzereinstellungen die Sprache in Ihrem Controller ein:

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);
}

Erstellen Sie dann in Ihrer Ansicht einen Sprachauswahlschalter:

@{
    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" />

Sie können auch einzelne Text-Strings überschreiben, während Sie noch von der Lokalisierung für andere Elemente profitieren:

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

Das PagingLocalizer Service behandelt kulturspezifische Formatierung automatisch. Wenn Sie einen ungültigen Sprachcode übergeben, fällt es anmutig auf Englisch zurück.

Für die HTMX-Integration möchten Sie die Sprache über Anfragen hinweg erhalten:

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

Dadurch wird sichergestellt, dass HTMX-Teilansichtsupdates die ausgewählte Sprache beibehalten.


JavaScript Modes

Eine der wichtigsten Verbesserungen in v1.0.0 ist die Einführung von flexiblen JavaScript-Modi. Zuvor hatten Sie eine boolesche Wahl: use-htmx="true" oder use-htmx="false"Jetzt haben Sie fünf verschiedene Modi, die jeweils für verschiedene Szenarien optimiert sind.

Verfügbare Modes

Hier ist die vollständige Aufschlüsselung der 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]

Lassen Sie uns jeden Modus in Aktion sehen:

1. HTMX-Modus (Standard)

<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>

Perfekt für dynamische Seiten-Updates ohne volle Seiten-Reloads. Dies ist der empfohlene Modus für moderne ASP.NET Core-Anwendungen.

2. HTMXWithAlpine-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>

Kombiniert HTMX zur Navigation mit Alpine.js für zusätzliche clientseitige Interaktivität. Verwenden Sie dies, wenn Sie reaktive UI-Elemente neben der Pagination benötigen (Ladeindikatoren, Animationen, Client-seitige Validierung).

3. Alpenmodus

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

Renders:

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

Pure Alpine.js ohne HTMX. Nützlich, wenn Sie bereits Alpine.js verwenden, aber keine HTMX-Abhängigkeiten wollen.

4. EinfachJS-Modus

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

Renders:

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

Keine Framework-Abhängigkeiten, nur Vanille JavaScript. Dieser Modus enthält auch einen Helfer für Seitengrößenänderungen:

@Html.PageSizeOnchangeSnippet()

Dies injiziert das notwendige JavaScript für die Bearbeitung von Seitengröße Dropdown-Änderungen ohne 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>

Null JavaScript erforderlich. Perfekt für:

  • Anforderungen an die Zugänglichkeit
  • Szenarien zur schrittweisen Verbesserung
  • Umgebungen, in denen JavaScript deaktiviert ist
  • SEO-kritische Seiten, auf denen Sie eine crawlerfreundliche Navigation wünschen

Die Schönheit dieses Systems ist, dass alle Modi bewahren Ihre vorhandenen AbfrageparameterOb Sie nach Kategorie filtern, suchen oder sortieren, die Paginierung hält Ihren Zustand automatisch aufrecht.

Migration aus Use-htmx

Für Rückwärtskompatibilität, die alte use-htmx Attribut funktioniert noch:

<!-- 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" -->

Ich empfehle jedoch die Migration zu den neuen js-mode Attribut für Klarheit:

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

AnzeigenVerbesserungen

Version 1.0.0 führt zwei wichtige ViewType-Additionen ein, die sich mit gängigen Real-World-Szenarien befassen.

Reiner Rückenwind {#reiner Rückenwind}

Zuvor, wenn Sie wollte TailwindCSS Styling, haben Sie die TailwindAndDaisy Ansicht, die DaisyUI Komponenten verwendet. Dies ist großartig, wenn Sie bereits DaisyUI verwenden, aber was, wenn Sie reinen Tailwind ohne die DaisyUI Abhängigkeit wollen?

Eingabe ViewType.Tailwind:

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

Dies macht nur mit Standard Tailwind Utility-Klassen:

<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>

Nein btn, badge, oder join Klassen – nur reiner Tailwind. Dadurch haben Sie die volle Kontrolle über Styling ohne Komponentenbibliothek Abhängigkeiten.

Vergleich:

. ViewType . CSS Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . |----------|---------------|-------------------|----------| | TailwindAndDaisy Projekte, die bereits DaisyUI verwenden | Tailwind TailwindCSSS-Keine Reine Tailwind-Projekte | Bootstrap Bootstrap-Komponenten Bootstrap-Projekte Bootstrap-Projekte | Plain Eingebettetes CSS-System Keine Keine Framework-Abhängigkeiten | NoJS Eingebettetes CSS-Programm Keines, keine JavaScript-Voraussetzungen

NeinJS-Modus

Das NoJS ViewType kombiniert Zero JavaScript mit dem Plain CSS Styling:

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

Wesentliche Unterschiede zu anderen Ansichtstypen:

  1. Navigation nutzt Ankerverbindungen, nicht Knöpfe:
<a href="/Products?page=2" class="pager-button">Next ›</a>
  1. Seitengröße Selektor ist ein Formular:
<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>

Das onchange="this.form.submit()" bietet Komfort, wenn JavaScript verfügbar ist, aber die <noscript> Taste sorgt für volle Funktionalität, wenn es nicht ist.


URL Parameter Erhaltung

Einer der frustrierendsten Aspekte von Paginationsimplementierungen ist der Verlust Ihrer Filter, Suchbegriffe oder Sortierreihenfolge beim Navigieren zwischen Seiten. Version 1.0.0 löst dies elegant durch Aufrechterhaltung aller Abfrageparameter mit Ausnahme der eigenen Parameter des Paginations-Steuerelements.

Diese Funktion funktioniert identisch über sowohl regelmäßige Pager als auch Fortsetzung Pager, und gegenüber alle JavaScript-Modi und ViewTypes.

So funktioniert es 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)}"));
}

Dieser Ansatz bedeutet:

Szenario 1: Suche + Pagination

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

Szenario 2: Sortierung + Paginierung

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

Szenario 3: Continuation Pager mit Filtern

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

Die gleiche Erhaltung funktioniert in Formularen (NoJS-Modus). Beim Rendern der Seitengröße enthält die Ansicht automatisch versteckte Eingaben für alle nicht-Paginationsparameter:

<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>

Das funktioniert nahtlos über alle JavaScript-Modi und alle ViewTypes. Sie müssen die Abfrage-String-Verbreitung nie manuell verwalten.


Migrationsleitfaden

Die Aktualisierung von vor-1.0-Versionen ist einfach, aber es gibt ein paar brechende Änderungen zu beachten.

Durchbrechen von Änderungen

1. use-htmx ist veraltet (aber funktioniert immer noch)

Alt:

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

Neu (empfohlen):

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

2. ViewType.TailwindAndDaisy verwendet nun vollständige DaisyUI-Komponenten

Wenn Sie ViewType.TailwindAndDaisy und wollen reinen Tailwind ohne DaisyUI:

Altes Verhalten (reiner Rückenwind):

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

Neu (um altes Verhalten zu bekommen):

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

Verwenden Sie weiter TailwindAndDaisy wenn Sie DaisyUI-Komponenten verwenden:

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

3. HTMX wurde auf 2.0.4 aktualisiert

Wenn Sie HTMX anderswo in Ihrer Anwendung verwenden, gewährleisten Sie die Kompatibilität mit HTMX 2.0.4. Die meisten HTMX 1.x-Code funktioniert unverändert, aber überprüfen Sie die HTMX 2.0 Migrationshandbuch für Randfälle.

Schritt-für-Schritt-Migration

Schritt 1: Aktualisieren des NuGet-Pakets

dotnet add package mostlylucid.pagingtaghelper --version 1.0.0

Schritt 2: Überprüfen Sie Ihren bestehenden Code

Suchen Sie Ihre Codebase nach use-htmx Attribute:

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

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

Schritt 3: Aktualisieren auf js-mode (empfohlen)

Ersetzen use-htmx mit 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" />

Schritt 4: Überprüfung TailwindAndDaisy Nutzung

Wenn Sie nicht DaisyUI installiert haben, aber mit TailwindAndDaisy:

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

Schritt 5: gründliche Prüfung

Führen Sie Ihre Bewerbung und Test:

  • Seitennavigation
  • Änderungen der Seitengröße
  • HTMX-Teilaktualisierungen (bei Verwendung von HTMX)
  • Konservierung von Filtern/Suchen
  • Mobile Reaktionsfähigkeit

Neue Funktionen für die Annahme

Sobald migriert, erwägen Sie die Annahme dieser neuen Funktionen:

Lokalisierung:

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

Continuation Pager (bei Verwendung von NoSQL):

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

NoJS-Modus (für die Zugänglichkeit):

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

Demo-Anwendung

Die Bibliothek enthält eine umfassende Demo-Anwendung, die alle Funktionen zeigt. Sie können es lokal ausführen oder es auf der Demo-Website (wird bald kommen).

Lokale Durchführung der Demo:

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

Navigieren zu https://localhost:5001 zu erkunden:

  1. Grundlegende Pagination mit Modell - Traditionelles Paging mit SQL-Pagination
  2. HTMX-Integration - Dynamische Seiten-Updates ohne volle Seite neu geladen
  3. Suche mit HTMX - Kombinierte Suche und Pagination
  4. Klares CSS - Keine Rahmenabhängigkeiten
  5. Reiner Rückenwind - RückwindCSS ohne DaisyUI
  6. Kein JavaScript - Voll funktionsfähige Null-JS-Pagination
  7. JavaScript-Modi - Alle fünf JS-Modi zeigten nebeneinander
  8. Seite sortieren - Sortierbare Header mit HTMX
  9. Seite sortieren Nein HTMX - Sortierbare Header mit ganzen Seitenlasten
  10. Seitengröße mit HTMX - Dynamische Änderungen der Seitengröße
  11. Seitengröße Nein HTMX - Seitengröße mit Formular-Einreichung
  12. Continuation Pager - NoSQL-Stil Token-basierte Pagination
  13. Lokalisierung - Sprachauswahl mit 8 Sprachen

Jede Demo enthält:

  • Arbeitsquellencode
  • Erklärung der Technik
  • Link zur GitHub-Implementierung
  • Interaktive Steuerungen zum Experimentieren

Schlußfolgerung

Die Version 1.0.0 ist ein wichtiger Meilenstein für die PagingTagHelper-Bibliothek. Was als einfacher Arbeitsbedarf begann, hat sich zu einer umfassenden, produktionsfertigen Paginationslösung entwickelt, die folgende Anforderungen erfüllt:

  • Traditionelle SQL-Pagination mit Offset/Limit
  • NoSQL Fortsetzung Token Pagination für Cosmos DB, DynamoDB, etc.
  • Mehrsprachige Lokalisierung für globales Publikum
  • Flexible JavaScript-Modi von HTMX zum Null-JavaScript
  • Mehrere CSS-Frameworks von DaisyUI zu reinem Tailwind zu keinem
  • Intelligente Parameterkonservierung über die gesamte Navigation
  • Unterstützung der uneingeschränkten Zugänglichkeit mit ARIA-Etiketten und Tastaturnavigation

Die Bibliothek wurde mit 1.7k+ Downloads kampfgetestet und ist serienreif. Alle 106 Gerätetests passieren, und die umfassende Demo-Anwendung zeigt reale Nutzungsmuster.

Was kommt als Nächstes?

Zukünftige Verbesserungen Ich überlege:

  • Zusätzliche CSS-Framework-Unterstützung (Material UI, Bulma)
  • Mehr Lokalisierungssprachen (Gemeinschaftsbeiträge willkommen!)
  • Serverseitige Blazor-Komponenten
  • Verbesserte Zugänglichkeit für große Datensätze

Erste Schritte

Installieren Sie über NuGet:

dotnet add package mostlylucid.pagingtaghelper --version 1.0.0

Schauen Sie sich die Dokumentation an:

Fragen, Feedback oder Beiträge? Öffnen Sie ein Thema auf GitHub oder erreichen Sie auf Twitter ~ scottgal ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~.

Glücklich paginieren!

Finding related posts...
logo

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