NOTA: Llegando pronto, solo dándole los toques finales. ¡Seguid a GitHub! .
¡Esto es sólo para mostrarles todo lo que YO SOY haciendo progreso con este control! Valdrá la pena esperar.
Después de meses de evolución y valiosos comentarios de la comunidad (5.7k+ descargas!), estoy emocionado de anunciar que la biblioteca PagingTagHelper ha llegado a la versión 1.0.0. Esto no es sólo un aumento de número de versión – representa una maduración completa de la biblioteca con características que lo hacen adecuado para aplicaciones de producción del mundo real.
Si has estado siguiendo esta serie, recordarás que empezamos con Búsqueda de huesos desnudos, añadido Cabeceras clasificables, y extraído controles de tamaño de página. La versión 1.0.0 toma todo lo que hemos aprendido y añade características empresariales críticas:
Vamos a sumergirnos en cada una de estas características y ver cómo trabajan juntos para crear una solución de paginación verdaderamente flexible.
La paginación tradicional funciona muy bien con bases de datos SQL donde se puede fácilmente SKIP y TAKE ¿Pero qué sucede cuando trabajas con bases de datos NoSQL como Cosmos DB, DynamoDB o Azure Table Storage? Estas bases de datos no soportan la paginación basada en offset, sino que usan tokens de continuación.
Así es como la paginación de continuación muestra difiere de la paginación tradicional:
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
Paging tradicional:
Llamado basado en tokens:
El nuevo <continuation-pager> tag helper hace que la implementación de la paginación basada en tokens sea sencilla. Primero, cree un modelo que implemente 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();
}
La interfaz es mínima pero potente. Echemos un vistazo a lo que hace cada propiedad:
NextPageToken: El token para recuperar la página siguiente (proporcionado por su base de datos)HasMoreResults: Booleano indicando si hay más páginasPageSize: Artículos por páginaCurrentPage: Número de página de sólo visualización para la interfaz de usuarioPageTokenHistory: Dictionary mapping page numbers to tokens for back navigationViewType: Qué marco CSS usar para renderizarAhora vamos a implementar una acción controladora que simula la paginación al estilo Cosmos DB:
[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);
}
Esta implementación muestra cómo el historial token permite la navegación hacia atrás. Sin él, la paginación token de continuación sólo soportaría botones "Next". Al mantener un diccionario de asignaciones de página a torre, podemos soportar la navegación "Anterior" y "Next".
Aquí está el flujo de acumulación simbólica visualizado:
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
En su vista Razor, usar el buscador de continuación es sencillo:
@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>
El ayudante de etiqueta automáticamente:
HasMoreResults es falsoEl genio del enfoque de historia simbólica es que es totalmente opcional. Si sólo necesitas navegación "Next" (desplazamiento infinito, por ejemplo), puedes ignorar la historia simbólica por completo:
<continuation-pager
model="Model"
enable-token-accumulation="false"
show-page-number="false" />
Esto representa sólo un botón "Next" sin indicadores de página o gestión del historial.
Para la navegación completa, el historial de tokens se seria automáticamente como JSON en la cadena de consulta. Así es como se ve una URL con el historial de tokens:
/Products?currentPage=3&pageSize=25&pageToken=abc123&tokenHistory=%7B%221%22%3A%22xyz789%22%2C%222%22%3A%22abc123%22%7D
Los tokenHistory parámetro contiene el diccionario codificado, haciendo navegación hacia atrás sin problemas.
Una de las mejoras de UX más importantes en el buscador de continuación es botones de página numerados. A medida que navega hacia adelante, el buscapersonas muestra los números de página de todas las páginas visitadas:
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)
Esto proporciona UX de paginación tradicional mientras mantiene la arquitectura de backend basada en tokens. La implementación almacena tokens para cada página visitada, permitiendo la navegación directa a cualquier página previamente visitada.
Limitando el crecimiento histórico:
Para evitar el uso de memoria sin límites, establecer max-history-pages (por defecto: 20):
<continuation-pager
model="Model"
max-history-pages="50"
show-page-number="true" />
Cuando se alcanza el límite, los tokens de página más antiguos se recortan automáticamente.
Esta es la característica más importante de la implementación del buscador de continuación.
Los tokens de continuación solo son válidos con el mismo contexto de consulta (filtros, tipos, búsquedas) que los generó. Usar un token con diferentes parámetros de consulta devolverá datos incorrectos o fallará por completo.
El paginador de continuación conserva automáticamente TODOS los parámetros de consulta excepto los suyos:
<!-- URL with filters -->
/Products?category=electronics&brand=acme&minPrice=100
<!-- After clicking Next -->
/Products?category=electronics&brand=acme&minPrice=100¤tPage=2&pageToken=xyz123&tokenHistory={...}
<!-- All filters preserved! Token is valid because query context matches. -->
Puede deshabilitar este comportamiento si es necesario:
<continuation-pager
model="Model"
preserve-query-parameters="false" />
Pero esto es fuertemente desalentado a menos que estés absolutamente seguro de que tus fichas no dependen del contexto de consulta.
Por qué esto importa:
Ejemplo de 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!
);
La preservación automática de parámetros del buscador de continuación garantiza que los tokens se utilicen siempre con su contexto de consulta original.
Las aplicaciones modernas sirven a audiencias globales, y los controles de paginación necesitan hablar el idioma de sus usuarios.La versión 1.0.0 incluye un completo soporte de localización integrado en la biblioteca.
La biblioteca envía con traducciones para 8 idiomas:
|------|----------|
| en Inglés (predeterminado)
| de Alemán (Deutsch)
| es Español Español
| fr Francés (francés) Francés (francés) Francés (francés) Francés (francés) Francés (francés) Francés (francés) Francés (francés) Francés (francés) Francés (francés)
| it Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano) Italiano (italiano)
| pt Portugués (Português)
| ja japonés (日本語)
| zh-Hans Chino simplificado ()
Todo el texto está localizado, incluyendo:
El sistema de localización está impulsado por .resx archivos de recursos, por lo que es fácil añadir sus propios idiomas. Todos los archivos de recursos están en mostlylucid.pagingtaghelper/Resources/.
El uso de la localización es sencillo. language atributo:
<paging
model="Model"
language="de"
show-summary="true"
first-last-navigation="true" />
Esto representa todo el texto en alemán:
<!-- 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>
Para el cambio dinámico de idioma basado en las preferencias del usuario, establezca el idioma en su controlador:
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);
}
Luego, en su vista, cree un selector de idioma:
@{
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" />
También puede anular cadenas de texto individuales mientras se beneficia de la localización para otros elementos:
<paging
model="Model"
language="ja"
previous-page-text="戻る"
next-page-text="次へ"
summary-template="全{TotalItems}件中 {StartItem}~{EndItem}件を表示" />
Los PagingLocalizer service maneja automáticamente el formato específico de cultura. Si pasas un código de idioma no válido, con gracia vuelve al inglés.
Para la integración HTMX, usted querrá preservar el lenguaje a través de las solicitudes:
<script>
htmx.on('htmx:configRequest', function(event) {
if (event.detail.path.includes('/Products')) {
event.detail.parameters.language = '@selectedLanguage';
}
});
</script>
Esto garantiza que las actualizaciones de la vista parcial de HTMX mantengan el idioma seleccionado.
Una de las mejoras más significativas en v1.0.0 es la introducción de modos JavaScript flexibles. Anteriormente, tenía una opción booleana: use-htmx="true" o use-htmx="false"Ahora tienes cinco modos distintos, cada uno optimizado para diferentes escenarios.
Aquí está el desglose completo de los modos JavaScript:
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]
Veamos cada modo en acción:
1. Modo HTMX (Defecto)
<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>
Perfecto para actualizaciones dinámicas de página sin recargas de página completa. Este es el modo recomendado para las aplicaciones modernas ASP.NET Core.
2. HTMXWithAlpine Mode
<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>
Combina HTMX para navegación con Alpine.js para interactividad adicional del lado del cliente. Úselo cuando necesite elementos de interfaz de usuario reactivos junto con la paginación (indicadores de carga, animaciones, validación del lado del cliente).
3. Modo alpino
<paging
model="Model"
js-mode="Alpine" />
Renders:
<button
x-data
@click="window.location.href = '/Products?page=2'">
Next ›
</button>
Pura Alpine.js sin HTMX. Útil cuando ya estás usando Alpine.js pero no quieres dependencias de HTMX.
4. Modo PlainJS
<paging
model="Model"
js-mode="PlainJS" />
Renders:
<button onclick="window.location.href = '/Products?page=2'">
Next ›
</button>
Sin dependencias de framework, solo JavaScript de vainilla. Este modo también incluye un ayudante para cambios en el tamaño de página:
@Html.PageSizeOnchangeSnippet()
Esto inyecta el JavaScript necesario para manejar los cambios desplegables del tamaño de la página sin HTMX.
5. Modo NoJS
<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>
Cero JavaScript necesario. Perfecto para:
La belleza de este sistema es que todos los modos conservan los parámetros de consulta existentesSi estás filtrando por categoría, buscando o ordenando, la paginación mantiene tu estado automáticamente.
Para la compatibilidad hacia atrás, el viejo use-htmx atributo sigue funcionando:
<!-- 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" -->
Sin embargo, recomiendo migrar a la nueva js-mode Atributo para la claridad:
<!-- New syntax (recommended) -->
<paging model="Model" js-mode="HTMX" />
<paging model="Model" js-mode="PlainJS" />
La versión 1.0.0 introduce dos adiciones importantes de ViewType que abordan escenarios comunes del mundo real.
Anteriormente, si querías el estilo TailwindCSS, tienes el TailwindAndDaisy vista que utiliza componentes DaisyUI. Esto es genial si ya estás usando DaisyUI, pero ¿qué pasa si quieres puro Tailwind sin la dependencia DaisyUI?
Entrar ViewType.Tailwind:
<paging
model="Model"
view-type="Tailwind" />
Esto renderiza usando solo clases de utilidad estándar de Coilwind:
<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>
No btn, badge, o join clases – sólo puro Tailwind. Esto le da un control completo sobre el estilo sin dependencias de la biblioteca de componentes.
Comparación:
VerType CSS Framework Biblioteca de componentes Caso de uso
|----------|---------------|-------------------|----------|
| TailwindAndDaisy TailwindCSS DaisyUI Proyectos que ya utilizan DaisyUI
| Tailwind TailwindCSS Ninguno Proyectos Pure Tailwind
| Bootstrap Bootstrap Bootstrap componentes Bootstrap proyectos
| Plain Ninguno
| NoJS CSS incrustado Ninguno Cero requisitos de JavaScript
Los NoJS ViewType combina cero JavaScript con el estilo CSS:
<paging
model="Model"
view-type="NoJS"
show-pagesize="true" />
Diferencias clave de otros tipos de vista:
<a href="/Products?page=2" class="pager-button">Next ›</a>
<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>
Los onchange="this.form.submit()" proporciona comodidad cuando JavaScript está disponible, pero el <noscript> el botón asegura la funcionalidad completa cuando no lo es.
Uno de los aspectos más frustrantes de las implementaciones de paginación es perder sus filtros, términos de búsqueda u orden de ordenación al navegar entre páginas. conservando automáticamente todos los parámetros de consulta excepto los propios parámetros del control de paginación.
Esta característica funciona idénticamente a través de tanto los buscapersonas regulares como los buscapersonas de continuación, y a través todos los modos JavaScript y ViewTypes.
Así es como funciona internamente:
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)}"));
}
Este enfoque significa:
Escenario 1: Búsqueda + Paginación
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
Escenario 2: Clasificación + Paginación
Initial URL: /Products?orderBy=price&descending=true&page=1
Click Page 3: /Products?orderBy=price&descending=true&page=3
Escenario 3: Página de continuación con filtros
Initial URL: /Products?category=electronics&brand=acme
Click Next: /Products?category=electronics&brand=acme¤tPage=2&pageToken=abc123&tokenHistory={...}
La misma preservación funciona en formularios (modo NoJS). Al renderizar el formulario de tamaño de página, la vista incluye automáticamente entradas ocultas para todos los parámetros que no son de paginación:
<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>
Esto funciona perfectamente a través de todos los modos JavaScript y todos los tipos de vista. Nunca tienes que administrar manualmente la propagación de la cadena de consulta.
Actualizar desde versiones pre-1.0 es sencillo, pero hay algunos cambios de ruptura de los que ser conscientes.
1. use-htmx está obsoleta (pero todavía funciona)
Viejo:
<paging model="Model" use-htmx="true" />
<paging model="Model" use-htmx="false" />
Nuevo (recomendado):
<paging model="Model" js-mode="HTMX" />
<paging model="Model" js-mode="PlainJS" />
2. ViewType.TailwindAndDaisy ahora utiliza componentes DaisyUI completos
Si estaba usando ViewType.TailwindAndDaisy y quieren puro viento de cola sin DaisyUI:
Comportamiento antiguo (puro viento de cola):
<paging model="Model" view-type="TailwindAndDaisy" />
Nuevo (para conseguir un comportamiento antiguo):
<paging model="Model" view-type="Tailwind" />
Siga utilizando TailwindAndDaisy si está usando componentes DaisyUI:
<paging model="Model" view-type="TailwindAndDaisy" />
<!-- Uses btn, join, badge, select, etc. -->
3. HTMX actualizado a 2.0.4
Si está usando HTMX en otra parte de su aplicación, asegúrese de que sea compatible con HTMX 2.0.4. La mayor parte del código HTMX 1.x funciona sin cambios, pero revise el Guía de migración HTMX 2.0 para los estuches de borde.
Paso 1: Actualizar el paquete NuGet
dotnet add package mostlylucid.pagingtaghelper --version 1.0.0
Paso 2: Revise su código existente
Busque su base de código para use-htmx atributos:
# PowerShell
Get-ChildItem -Recurse -Include *.cshtml | Select-String "use-htmx"
# Bash/Git Bash
grep -r "use-htmx" --include="*.cshtml" .
Paso 3: Actualizar a js-mode (recomendado)
Reemplazar use-htmx con 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" />
Paso 4: Revisar el uso de TailwindAndDaisy
Si usted no tiene DaisyUI instalado pero estaban utilizando TailwindAndDaisy:
- <paging model="Model" view-type="TailwindAndDaisy" />
+ <paging model="Model" view-type="Tailwind" />
Paso 5: Prueba a fondo
Ejecute su aplicación y prueba:
Una vez migrado, considere la adopción de estas nuevas características:
Localización:
<paging
model="Model"
language="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName" />
Página de continuación (si se utiliza NoSQL):
<continuation-pager
model="Model"
htmx-target="#results-container"
show-page-number="true" />
Modo no JS (para accesibilidad):
<paging model="Model" js-mode="NoJS" />
La biblioteca incluye una completa aplicación de demostración que muestra todas las características. Puede ejecutarlo localmente o verlo en el sitio de demostración (en breve).
Ejecutando la Demo Localmente:
git clone https://github.com/scottgal/mostlylucid.pagingtaghelper.git
cd mostlylucid.pagingtaghelper/mostlylucid.pagingtaghelper.sample
dotnet run
Navega hasta https://localhost:5001 para explorar:
Cada demo incluye:
La versión 1.0.0 representa un hito importante para la biblioteca PagingTagHelper. Lo que comenzó como un simple requisito de trabajo se ha convertido en una solución de paginación completa y lista para la producción que maneja:
La biblioteca ha sido probada con 1.7k+ descargas y está lista para el uso de producción. Todas las pruebas de 106 unidades pasan, y la completa aplicación demo muestra patrones de uso en el mundo real.
Mejoras futuras que estoy considerando:
Instalar a través de NuGet:
dotnet add package mostlylucid.pagingtaghelper --version 1.0.0
Echa un vistazo a la documentación:
¿Preguntas, comentarios o contribuciones? Abra un número en GitHub o contacte en Twitter @scottgal.
¡Feliz paginación!
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.