Back to "PagingTagHelper v1.0.0: Enterprise-Ready Pagination för modern ASP.NET Core"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Alpine.js ASP.NET Core HTMX Javascript Nuget PagingTagHelper TagHelper

PagingTagHelper v1.0.0: Enterprise-Ready Pagination för modern ASP.NET Core

Friday, 07 November 2025

ANMÄRKNING: KOMMUNKTION, bara sätta de avslutande touchs till det. Följ med på GitHub! .

Detta är bara för att visa er alla JAG gör framsteg med denna kontroll! Det kommer att vara värt väntan.

Inledning

Efter månader av utveckling och värdefull feedback från samhället (5.7k+ nedladdningar!), Jag är glad att meddela att PagingTagHelper biblioteket har nått version 1.0.0. Detta är inte bara en version nummer bulle – det representerar en fullständig mognad av biblioteket med funktioner som gör det lämpligt för verkliga, produktionsapplikationer.

Om du har följt den här serien, kommer du ihåg att vi började med Skalning av barben, tillagd sorterabara rubriker, och extraherad kontroller av sidstorleken. Version 1.0.0 tar allt vi har lärt oss och lägger till kritiska företag funktioner:

  • Fortsättning med Token Pagination för NoSQL-databaser (Cosmos DB, DynamoDB, Azure Table Storage)
  • Lokalisering på flera språk stödja 8 språk ur rutan
  • Flexibla JavaScript- lägen från HTMX till noll-JavaScript
  • Ren tailwind vyer utan DaisyUI beroenden
  • Smart URL Parameterbevarande över hela navigeringen
  • HTMX 2,0.4 uppgradering med bakåtkompatibilitet

Låt oss dyka in i var och en av dessa funktioner och se hur de arbetar tillsammans för att skapa en verkligt flexibel paginering lösning.

Hämta Hämta

Fortsättning Token Pagination

Traditionell paginering fungerar vackert med SQL-databaser där du enkelt kan SKIP och TAKE Men vad händer när du arbetar med NoSQL-databaser som Cosmos DB, DynamoDB eller Azure Table Storage? Dessa databaser stöder inte offsetbaserad pagination – istället använder de sig av fortsättningspolletter.

Understanding Token-based Pagination

Här är hur fortsättning token pagination skiljer sig från traditionell personsökning:

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

Traditionell packning:

  • Du anger exakt vilka poster som ska hämtas (OFFSET/LIMIT)
  • Du kan hoppa till vilken sida som helst direkt
  • Databasen måste söka igenom alla tidigare poster

Token-baserad packning:

  • Databasen returnerar en ogenomskinlig token som representerar "var man ska fortsätta"
  • Token-formatet är databasspecifikt och ogenomskinligt för kunden
  • Framåt navigering är naturlig, bakåt navigering kräver symbolisk historik

Genomförande av fortsättningssidan {#fortsättningssidans genomförande}

Det nya <continuation-pager> tag helper gör genomförandet token-baserad pagination enkelt. Först, skapa en modell som implementerar 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();
}

Gränssnittet är minimalt men kraftfullt. Låt oss titta på vad varje egenskap gör:

  • NextPageToken: Den token att hämta nästa sida (som tillhandahålls av din databas)
  • HasMoreResults: Booleska indikerar om det finns fler sidor
  • PageSize: Föremål per sida
  • CurrentPage: Visa endast sidnummer för UI
  • PageTokenHistory: Dictionary mappa sidnummer till polletter för bakåt navigering
  • ViewType: Vilket CSS-ramverk att använda för rendering

Låt oss nu genomföra en controller åtgärd som simulerar Cosmos DB-stil pagination:

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

Detta genomförande visar hur token historia möjliggör bakåt navigering. Utan det, fortsättning token pagination skulle bara stödja "Next" knappar. Genom att underhålla en ordbok av sida till token kartläggningar, kan vi stödja både "Föregående" och "Next" navigering.

Här är flödet av symbolisk ackumulation visualiserade:

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

I din Razor-vy är det enkelt att använda fortsättningssökaren:

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

Tagghjälparen automatiskt:

  • Serializes token historia till frågeparametrar
  • Bygger navigationswebbadresser med riktiga polletter
  • Inaktiverar "Föregående" när på sidan 1
  • Inaktiverar "Nästa" när HasMoreResults är falskt
  • Bevarar alla andra frågeparametrar (sök, filter, etc.)

Tokens historia för bakåtgående navigering

Genialet med den symboliska historien tillvägagångssätt är att det är helt frivilligt. Om du bara behöver "Next" navigering (oändlig skriftrulle, till exempel), kan du ignorera symboliska historia helt:

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

Detta innebär bara en "Nästa" knapp utan sidindikatorer eller historikhantering.

För full navigering serialiseras tokenhistoriken automatiskt som JSON i frågesträngen. Så här ser en URL ut med symbolisk historik:

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

och tokenHistory parametern innehåller den kodade ordboken, vilket gör bakåt navigation sömlös.

Numrerad sidnavigering

En av de viktigaste UX förbättringarna i fortsättningssökaren är numrerade sidknapparNär du navigerar framåt visar sökaren klickbara sidnummer för alla besökta sidor:

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)

Detta ger traditionell paginering UX samtidigt som du behåller token-baserad bakgrundsarkitektur. Implementeringen lagrar polletter för varje besökt sida, vilket tillåter direkt navigering till någon tidigare åtkomlig sida.

Begränsad tillväxt i historien:

För att förhindra användning av obegränsat minne, set max-history-pages (standard: 20):

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

När gränsen är nådd klipps de äldsta sidpolletterna automatiskt.

Kritiska: Förfrågan Parameterbevarande

Detta är det viktigaste inslaget i fortsatt personsökarimplementering.

Fortsättning tokens är bara giltiga med samma frågekontext (filter, typer, sökningar) som genererade dem. Att använda en token med olika frågeparametrar returnerar felaktig data eller misslyckas helt.

Fortsättningssökaren bevarar automatiskt ALLA frågeparametrar utom sina egna:

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

Du kan inaktivera detta beteende om det behövs:

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

Men det här är kraftigt modfälld Om du inte är helt säker på att dina polletter inte beror på frågesammanhang.

Varför detta betyder något:

Exempel på Cosmos 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!
);

Fortsättningssökarens automatiska parameterbevarande säkerställer att polletter alltid används i sitt ursprungliga frågesammanhang.


Lokaliseringsstöd

Moderna applikationer tjänar global publik, och paginering kontroller måste tala dina användares språk. Version 1.0.0 innehåller omfattande localization stöd inbyggt rätt i biblioteket.

Inbyggda språk {#inbyggda språk}

Biblioteket skickar med översättningar för 8 språk:

Obligatoriskt ("Kod") |------|----------| | en på engelska (standard) på engelska | de på tyska (Deutsch) på engelska | es på spanska (Español) | fr på franska (Français) | it på italienska (Italien) | pt på portugisiska (Português) | ja Japanska (engelska) och japanska (engelska) | zh-Hans på kinesiska förenklas ()

All text är lokaliserad, inklusive:

  • Föregående/Nästa/Första/Sista knappetiketter
  • Sammanfattning av sidan ("Visa X till Y av Z objekt")
  • ARIA-etiketter för tillgänglighet
  • Sidstorleksetikett ("Items per sida")

Lokaliseringssystemet drivs av .resx resursfiler, vilket gör det enkelt att lägga till dina egna språk. Alla resursfiler är i mostlylucid.pagingtaghelper/Resources/.

Lokalisering Användning {# localization- användning}

Att använda lokalisering är enkelt. Lägg bara till language egenskap:

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

Detta gör all text på tyska:

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

För dynamisk språkbyte baserat på användarinställningar, ange språket i din controller:

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

Sedan, i din vy, skapa en språkväljare:

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

Du kan också åsidosätta enskilda textsträngar medan du fortfarande drar nytta av lokalisering för andra element:

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

och PagingLocalizer service hanterar kulturspecifik formatering automatiskt. Om du passerar en ogiltig språkkod, det graciöst faller tillbaka till engelska.

För HTMX integration, vill du bevara språket över förfrågningar:

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

Detta säkerställer att HTMX partiella vyuppdateringar upprätthåller det valda språket.


JavaScript- lägen

En av de mest betydande förbättringarna i v1.0.0 är införandet av flexibla JavaScript-lägen. Tidigare hade du ett booleskt val: use-htmx="true" eller use-htmx="false". Nu har du fem olika lägen, var och en optimerad för olika scenarier.

Tillgängliga lägen {# tillgängliga- lägen}

Här är den fullständiga uppdelningen av JavaScript-lägen:

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]

Låt oss se varje läge i funktion:

1. HTMX-läge (förvalt)

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

Rendrare:

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

Perfekt för dynamiska siduppdateringar utan helsida omladdningar. Detta är det rekommenderade läget för moderna ASP.NET Core-applikationer.

2. HTMXMed alpinläge

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

Rendrare:

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

Kombinerar HTMX för navigering med Alpine.js för ytterligare interaktivitet på klientsidan. Använd detta när du behöver reaktiva UI-element vid sidan av paginering (ladda in indikatorer, animationer, validering på klientsidan).

3. Alpina läge

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

Rendrare:

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

Pure Alpine.js utan HTMX. Användbar när du redan använder Alpine.js men inte vill HTMX beroenden.

4. Enkelt läge för JS

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

Rendrare:

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

Inga ramberoenden, bara vanilj JavaScript. Detta läge innehåller också en hjälpare för sidstorleksändringar:

@Html.PageSizeOnchangeSnippet()

Detta injicerar det nödvändiga JavaScriptet för att hantera ändringar i sidstorlek utan HTMX.

5. NoJS- läge

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

Rendrare:

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

Noll JavaScript krävs. Perfekt för:

  • Tillgänglighetskrav
  • Scenarier för progressiv förbättring
  • Miljöer där JavaScript är inaktiverat
  • SEO-kritiska sidor där du vill ha krypande-vänlig navigering

Skönheten i detta system är att alla lägen bevara dina befintliga sökparametrar. Oavsett om du filtrerar efter kategori, sökning, eller sortering, håller pagineringen ditt tillstånd automatiskt.

Migrering från use-htmx

För bakåtkompatibilitet, den gamla use-htmx Egenskapen fortfarande fungerar:

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

Jag rekommenderar dock att jag flyttar till det nya js-mode attribut för tydlighet:

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

VisaType förbättringar

Version 1.0.0 introducerar två viktiga ViewType tillägg som behandlar vanliga verkliga scenarier.

Ren tailwind

Tidigare, om du ville ha TailwindCSS styling, fick du TailwindAndDaisy Visa som använder DaisyUI komponenter. Detta är bra om du redan använder DaisyUI, men tänk om du vill ha ren Tailwind utan DaisyUI beroende?

Ange ViewType.Tailwind:

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

Detta innebär att endast använda standard Tailwind verktygsklasser:

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

Ej tillämpligt btn, badge, eller join klasser – bara ren Tailwind. Detta ger dig fullständig kontroll över styling utan komponentbibliotek beroenden.

Jämförelse:

på visningstyp på CSS-ramverket på komponentbiblioteket på användningsfall på plats |----------|---------------|-------------------|----------| | TailwindAndDaisy på TailwindCSS på DaisyUI på projekt som redan använder DaisyUI | Tailwind på TailwindCSS på inget sätt på ren Tailwind projekt | Bootstrap Komponenter till bootstrap-bootstrap-bootstrap-projekt | Plain Inbäddade CSS och ingen på inga ramar beroenden på | NoJS Inbäddade CSS med noll JavaScript-krav

NoJS- läge {# nojs- läge}

och NoJS ViewType kombinerar noll JavaScript med vanlig CSS-styling:

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

Viktiga skillnader från andra vytyper:

  1. Navigation använder ankarlänkar, inte knappar:
<a href="/Products?page=2" class="pager-button">Next ›</a>
  1. Sidstorleksväljaren är ett formulär:
<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>

och onchange="this.form.submit()" ger bekvämlighet när JavaScript är tillgängligt, men <noscript> Knappen säkerställer full funktionalitet när den inte är det.


Webbadress Parameterbevarande

En av de mest frustrerande aspekterna av paginering implementationer är att förlora dina filter, sökord, eller sortera ordning när du navigerar mellan sidor. Version 1.0.0 löser detta elegant genom automatiskt bevara alla frågeparametrar utom pagineringskontrollens egna parametrar.

Denna funktion fungerar identiskt över både vanliga personsökare och fortsättningssökare, och över alla JavaScript-lägen och visningstyper.

Så här fungerar det internt:

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

Detta tillvägagångssätt innebär följande:

Scenario 1: Sök + 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

Scenario 2: Sortera + Pagination

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

Scenario 3: Fortsättningssökare med filter

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

Samma bevarande fungerar i formar (NoJS-läge). När sidstorleksformuläret återges innehåller vyn automatiskt dolda indata för alla parametrar som inte är paginerade:

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

Detta fungerar sömlöst över alla JavaScript-lägen och alla ViewTypes. Du behöver aldrig hantera manuellt frågesträng utbredning.


Migrationsguide

Uppgradering från pre-1.0 versioner är enkelt, men det finns några bryta förändringar att vara medveten om.

Bryta förändringar

1. use-htmx är deprecerad (men fortfarande fungerar)

Gamla:

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

Nytt (rekommenderas):

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

2. ViewType.TailwindAndDaisy använder nu hela DaisyUI komponenter

Om du använde ViewType.TailwindAndDaisy Och vill ha ren Tailwind utan DaisyUI:

Gammalt beteende (rent Tailwind):

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

Nytt (för att få gammalt beteende):

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

Fortsätt använda TailwindAndDaisy Om du använder DaisyUI-komponenter:

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

3. HTMX uppgraderas till 2.0.4

Om du använder HTMX någon annanstans i din applikation, säkerställa kompatibilitet med HTMX 2.0.4. De flesta HTMX 1.x kod fungerar oförändrad, men granska HTMX 2.0 migrationsguide för kantfodral.

Steg-för-steg-migrering

Steg 1: Uppdatera NuGet-paketet

dotnet add package mostlylucid.pagingtaghelper --version 1.0.0

Steg 2: Granska din befintliga kod

Sök i din kodbas efter use-htmx egenskaper:

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

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

Steg 3: Uppdatera till js-mode (rekommenderas)

Ersätt use-htmx där 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" />

Steg 4: Granska TailwindAndDaisy användning

Om du inte har DaisyUI installerat men använt TailwindAndDaisy:

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

Steg 5: Testa noggrant

Kör din ansökan och testa:

  • Sidnavigering
  • Ändrad sidstorlek
  • HTMX partiella uppdateringar (om HTMX används)
  • Filter/sökbevarande
  • Mobil lyhördhet

Nya egenskaper att anta

När jag har flyttat, överväga att anta dessa nya funktioner:

Lokalisering:

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

Fortsättningssida (om NoSQL används):

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

NoJS-läge (för tillgänglighet):

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

Demoprogram {#demo- ansökan}

Biblioteket innehåller ett omfattande demoprogram som visar alla funktioner. Du kan köra det lokalt eller visa det på demo webbplats (kommer snart).

Köra Demo lokalt:

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

Navigera till https://localhost:5001 för att undersöka:

  1. Grundläggande paginering med modell - Traditionell personsökning med SQL- stil paginering
  2. Integrering av HTMX - Dynamiska siduppdateringar utan helsida laddar om
  3. Sök med HTMX - Kombinerad sökning och paginering
  4. Enkelt CSS - Inga ramberoenden
  5. Ren tailwind - TailwindCSS utan DaisyUI
  6. Inget JavaScript - Fullt fungerande noll JS pagination
  7. JavaScript- lägen - Alla fem JS-lägen demonstrerade sida vid sida
  8. Sidsortering - Sorterade rubriker med HTMX
  9. Sidsortera ingen HTMX - Sorterade rubriker med helsidasbelastningar
  10. Sidstorlek med HTMX - Dynamiska sidstorleksändringar
  11. Sidstorlek nr HTMX - Sidstorlek med ansökningsblankett
  12. Fortsättningssökare - NoSQL-stil token-baserad paginering
  13. Lokalisering - Språkväljare med 8 språk

Varje demo innehåller:

  • Kod för arbetskälla
  • Förklaring av tekniken
  • Länk till genomförandet av GitHub
  • Interaktiva kontroller för att experimentera

Slutsatser

Version 1.0.0 är en viktig milstolpe för biblioteket PagingTagHelper. Det som började som ett enkelt arbetskrav har utvecklats till en omfattande, produktionsklar pagineringslösning som hanterar:

  • Traditionell SQL-paginering med motvikt/begränsning
  • NoSQL- fortsättning token pagination för Cosmos DB, DynamoDB, etc.
  • Lokalisering på flera språk för global publik
  • Flexibla JavaScript-lägen från HTMX till noll-JavaScript
  • Flera CSS-ramverk från DaisyUI till ren Tailwind till ingen
  • Smart parameterbevarande över hela navigeringen
  • Stöd för fullständig tillgänglighet med ARIA-etiketter och tangentbordsnavigering

Biblioteket har testats i strid med 1,7k+ nedladdningar och är redo för produktionsanvändning. Alla 106 enhetstester godkänns och den omfattande demoapplikationen visar upp verkliga användningsmönster.

Vad är nästa?

Framtida förbättringar Jag överväger:

  • Ytterligare CSS-ramstöd (Material UI, Bulma)
  • Fler lokalanpassningsspråk (gemenskapens bidrag välkomnas!)
  • Komponenter till serversidan Blazor
  • Förbättrad tillgänglighet för stora datauppsättningar

Kom igång

Installera via NuGet:

dotnet add package mostlylucid.pagingtaghelper --version 1.0.0

Kolla in dokumentationen:

Frågor, feedback eller bidrag? Öppna ett nummer på GitHub eller nå ut på Twitter @ scottgal.

Glad paginering!

logo

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