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

<datetime class="hidden">2025-11-07T19:12</datetime>

<!--category-- Nuget, ASP.NET Core, HTMX, Alpine.js, Javascript, TagHelper, PagingTagHelper -->
> HINWEIS: KOMING SOON, nur den letzten Schliff zu setzen. [Folgen Sie dem GitHub! ](https://github.com/scottgal/mostlylucid.pagingtaghelper).

**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](https://www.mostlylucid.net/blog/pagingtaghelper), hinzugefügt [sortierbare Kopfzeilen](https://www.mostlylucid.net/blog/pagingtaghelperpt11), und extrahiert [Seitengrößenregler](https://www.mostlylucid.net/blog/pagingtaghelperpt2). 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](https://img.shields.io/nuget/v/mostlylucid.pagingtaghelper.svg)](https://www.nuget.org/packages/mostlylucid.pagingtaghelper)
[![NuGet](https://img.shields.io/nuget/dt/mostlylucid.pagingtaghelper.svg)](https://www.nuget.org/packages/mostlylucid.pagingtaghelper)

[TOC]

## Fortsetzung Token-Pagination {#Continuation-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 {#Token-Based-Pagination verstehen}

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

```mermaid
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 {#Continuation-Pager-Implementation}

Das neue `<continuation-pager>` tag helper macht die Umsetzung token-basierte pagination einfach. Zuerst, erstellen Sie ein Modell, das implementiert `IContinuationPagingModel`:

```csharp
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:

```csharp
[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:

```mermaid
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:

```razor
@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>$@product.Price.ToString("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 {#token-history}

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:

```razor
<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 {#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):

```razor
<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 {#query-parameter-reservation}

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

```html
<!-- 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:

```razor
<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:

```csharp
// 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 {#lokalisierungs-support}

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 {#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-useage}

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

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

Damit wird der gesamte Text in deutscher Sprache wiedergegeben:

```html
<!-- 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:

```csharp
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:

```razor
@{
    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:

```razor
<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:

```html
<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 {#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 {#available-modes}

Hier ist die vollständige Aufschlüsselung der JavaScript-Modi:

```mermaid
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)**

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

Renders:

```html
<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**

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

Renders:

```html
<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**

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

Renders:

```html
<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**

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

Renders:

```html
<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:

```razor
@Html.PageSizeOnchangeSnippet()
```

Dies injiziert das notwendige JavaScript für die Bearbeitung von Seitengröße Dropdown-Änderungen ohne HTMX.

**5. NoJS-Modus**

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

Renders:

```html
<!-- 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 Abfrageparameter**Ob Sie nach Kategorie filtern, suchen oder sortieren, die Paginierung hält Ihren Zustand automatisch aufrecht.

### Migration aus Use-htmx {#Migration aus Use-htmx}

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

```razor
<!-- 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:

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

---


## AnzeigenVerbesserungen {#viewtype-enhancements}

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

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

Dies macht nur mit Standard Tailwind Utility-Klassen:

```html
<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 {#nojs-Modus}

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

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

Wesentliche Unterschiede zu anderen Ansichtstypen:

1. **Navigation nutzt Ankerverbindungen**, nicht Knöpfe:

```html
<a href="/Products?page=2" class="pager-button">Next ›</a>
```

2. **Seitengröße Selektor ist ein Formular**:

```html
<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 {#url-Parameter-Reservierung}

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:

```csharp
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:

```razor
<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 {#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:

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

Neu (empfohlen):

```razor
<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):

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

Neu (um altes Verhalten zu bekommen):

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

Verwenden Sie weiter `TailwindAndDaisy` wenn Sie DaisyUI-Komponenten verwenden:

```razor
<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](https://htmx.org/migration-guide-htmx-1/) für Randfälle.

### Schritt-für-Schritt-Migration

**Schritt 1: Aktualisieren des NuGet-Pakets**

```bash
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:

```bash
# 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`:

```diff
- <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`:

```diff
- <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:**

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

**Continuation Pager (bei Verwendung von NoSQL):**

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

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

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

---


## Demo-Anwendung {#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](https://paging-demo.mostlylucid.net) (wird bald kommen).

**Lokale Durchführung der Demo:**

```bash
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:

```bash
dotnet add package mostlylucid.pagingtaghelper --version 1.0.0
```

Schauen Sie sich die Dokumentation an:

- [GitHub-Repository](https://github.com/scottgal/mostlylucid.pagingtaghelper)
- [NuGet-Paket](https://www.nuget.org/packages/mostlylucid.pagingtaghelper)
- [Vollständige Dokumentation](https://github.com/scottgal/mostlylucid.pagingtaghelper/tree/main/docs)

Fragen, Feedback oder Beiträge? Öffnen Sie ein Thema auf GitHub oder erreichen Sie auf Twitter [~ scottgal ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~](https://twitter.com/scottgal).

Glücklich paginieren!