# Ein Paging-View-Komponenten-ASP-.-NET Core Tag Helper

<datetime class="hidden">2025-03-25T22:12</datetime>

<!--category-- ASP.NET Core, TagHelper, PagingTagHelper -->
# Einleitung

Als Teil meines fortlaufenden Salbeis mit meinem Paging Tag Helper habe ich nun PageSize in einen eigenen Tag-Helfer getrennt..- Dies ist, um den Tag Helfer flexibler zu machen und komplexere Paging Szenarien zu ermöglichen..

**Wiederum sind dies alle Werke, die bereits im Gange sind,- [bis zu 1 0.9.0](https://www.nuget.org/packages/mostlylucid.pagingtaghelper#readme-body-tab) zum Zeitpunkt des Schreibens, aber VERWENDUNG AUF IHREM EIGENEN RISK-. . Es ist kostenlos, aber NICHT YET der Release-Qualität IMHO . [Vollproben](https://taghelpersample.mostlylucid.net/PageSizeWithHTMX) noch nicht !)**

Siehe [Code für den Tag-Helfer hier](https://github.com/scottgal/mostlylucid.pagingtaghelper/blob/main/mostlylucid.pagingtaghelper/Components/PageSizeTagHelper.cs).

[TOC]

# Die Seitengröße Tag Helfer

Wie bei meinen anderen Taghelpern ist dies für einen Einsatz konzipiert.-.case Ich begegne oft, wie man einfach Seitengröße in Ergebnislisten umschaltet.
Auf den ersten Blick scheint dies ziemlich einfach, aber es kann TRICKY werden, sobald Sie HTMX in den Mix hinzufügen.

## Anforderungen

So für meinen Anwendungsfall die Anforderungen waren die folgenden

1. Ermöglicht die einfache Verwendung für teh Entwickler ist so viel wie möglich mit einem einfachen Seitenmodell konfiguriert.
2. Unterstützt das Aktualisieren der Seitengröße mit HTMX, was bedeutet, dass es ' ist in der Lage zurück zu posten *unter Beibehaltung aller Abfrage-String-Parameter*.
   Dies ist wichtig, da es dem Benutzer erlaubt, die Seitengröße zu ändern, ohne andere Filter zu verlieren, die sie angewendet haben.
3. Hat einen etwas dynamischen Bereich der Seitengrössen, einschließlich einer Option von ).

## Der Tag Helfer

In diesem Sinne habe ich den TagHelper wie folgt entworfen:

```html
<page-size
    hx-target="#list"
    hx-swap="innerHTML"
    model="Model">
    
</page-size>
<div id="list">
    <partial name="_ResultsList" model="Model"/>
</div>
```

Also in diesem Fall wieder nimmt es meine Standard-Paging-Modell

```csharp
public interface IPagingModel
{
    public int Page { get; set; }
    public int TotalItems { get; set; }
    public int PageSize { get; set; }

    public ViewType ViewType { get; set; }
    
    public string LinkUrl { get; set; }
}
```

Das stellt die `Page`, `TotalItems`, `PageSize`, `ViewType` und `LinkUrl` für das Paging-.

Sie können diese auch in Form von Attributen an das Modell angeben.

```html

<page-size
    hx-target="#list"
    hx-swap="innerHTML"
    total-items="100"
    page-size="10"
    view-type="DaisyAndTailwind">
</page-size>
```

Bei der `hx-target` hier ist das Ziel für die HTMX-Anfrage und die `hx-swap` ist das Ziel für die Antwort

In diesem Fall verwenden sie die angegebene `PageSize` und `TotalItems` und `ViewType` zum Rendern des Seitengröße-Selektors

## JS Shenanigans

Leider dieses Mal um ich hatte eine Anforderung Ich konnte nicht behandeln rein Server-Seite..... Nämlich wollte ich Query String Paramater in der Pagesize Änderung Anfrage beibehalten.
Um dies zu erreichen, habe ich zwei Teile von JS, die in der Steuerung je nachdem HTMX verwendet wird laden ooder nicht

```csharp
@if (Model.UseHtmx)
{
@Html.HTMXPageSizeChange()
}
else
{
    @Html.PageSizeOnchangeSnippet()
}
```

Wenn HTMX verwendet wird, wird dies in der Seite wiedergegeben.

```javascript
(() => {
    if (window.__pageSizeListenerAdded) return;

    document.addEventListener('htmx:configRequest', event => {
        const { elt } = event.detail;
        if (elt?.matches('[name="pageSize"]')) {
            const params = new URLSearchParams(window.location.search);
            params.set('pageSize', elt.value); // This will update or add the pageSize param
            event.detail.parameters = Object.fromEntries(params.entries());
        }
    });
    window.__pageSizeListenerAdded = true;
})();
```

Das ist ein einfaches Stück JavaScript, das aktuelle Querystring-Parameter in die Request-Anfrage injiziert.

Alternativ, wenn HTMX NICHT aktiviert ist, wird dies rendern.

```javascript
(function attachPageSizeListener() {
    // Ensure we only attach once if this script is included multiple times
    if (window.__pageSizeListenerAttached) return;
    window.__pageSizeListenerAttached = true;

    // Wait for the DOM to be fully loaded
    document.addEventListener("DOMContentLoaded", () => {
        document.body.addEventListener("change", handlePageSizeChange);
    });

    function handlePageSizeChange(event) {
        // Check if the changed element is a page-size <select> inside .page-size-container
        const select = event.target.closest(".page-size-container select[name='pageSize']");
        if (!select) return;

        const container = select.closest(".page-size-container");
        if (!container) return;

        // Default to "true" if there's no .useHtmx input
        const useHtmx = container.querySelector("input.useHtmx")?.value === "true";
        if (useHtmx) {
            // If using HTMX, we do nothing—HTMX will handle the request
            return;
        }

        // Either use a linkUrl from the container or the current page URL
        const linkUrl = container.querySelector("input.linkUrl")?.value || window.location.href;
        const url = new URL(linkUrl, window.location.origin);

        // Copy existing query params from current location
        const existingParams = new URLSearchParams(window.location.search);
        for (const [key, value] of existingParams.entries()) {
            url.searchParams.set(key, value);
        }

        // If user picked the same pageSize as what's already in the URL, do nothing
        if (url.searchParams.get("pageSize") === select.value) {
            return;
        }

        // Update the pageSize param
        url.searchParams.set("pageSize", select.value);

        // Redirect
        window.location.href = url.toString();
    }
})();
```

Eine Änderung, die I'-ll lilely machen, ändert den Namen der Variable ;. `__pageSizeListenerAttached ` wie es ist ein bisschen zu generisch UND ich verwende es sowohl für HTMX als auch für Nicht--.HTMX Anfragen

## Der CODE

Der Tag-Helfer selbst ist ziemlich einfach, der Hauptcode bevölkert nur ein paar Eigenschaften und dann rendert die Ansicht Komponente.

```csharp
 public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        // We want to render a <div> by default
        output.TagName = "div";

        // Remove any leftover attributes that we handle ourselves
        RemoveUnwantedAttributes(output);

        // Determine final PagerId
        var pagerId = PagerId ?? PageSizeModel?.PagerId ?? $"pager-{Guid.NewGuid():N}";
        // Assign ID to the outer div
        output.Attributes.SetAttribute("id", pagerId);
        // Build or fallback to a link URL
        var finalLinkUrl = BuildLinkUrl();
        if (string.IsNullOrEmpty(finalLinkUrl))
        {
            // If we can't build a URL, show a fallback message or short-circuit
            output.Content.SetHtmlContent(
                "<p style=\"color:red\">No valid link URL found for PageSize control.</p>");
            return;
        }

        var finalPageSize = PageSize ?? PageSizeModel?.PageSize ?? 10;
        // Clamp the page size to MaxPageSize

        var finalTotalItems = TotalItems ?? Model?.TotalItems ?? PageSizeModel?.TotalItems ?? 0;
        if (finalTotalItems == 0) throw new ArgumentNullException(nameof(finalTotalItems), "TotalItems is required");
        var maxPageSize = Math.Min(finalTotalItems, MaxPageSize);

        // Fallback to model's properties if not set
        var finalViewType = PageSizeModel?.ViewType ?? Model?.ViewType ?? ViewType;

        var pageSizeSteps = new int[] { 10, 25, 50, 75, 100, 125, 150, 200, 250, 500, 1000 };

        
        if (!string.IsNullOrEmpty(PageSizeSteps))
            pageSizeSteps = PageSizeSteps.Split(',').Select(s =>

            {
                if (int.TryParse(s, out var result))
                    return result;
                else
                {
                    throw new ArgumentException("Invalid page size step", nameof(PageSizeSteps));
                }
            }).ToArray();


        var pageSizes = CalculatePageSizes(finalTotalItems, maxPageSize, pageSizeSteps);

        var useHtmx = PageSizeModel?.UseHtmx ?? UseHtmx;
        var useLocalView = PageSizeModel?.UseLocalView ?? UseLocalView;

        // Acquire the IViewComponentHelper
        var viewComponentHelper = ViewContext.HttpContext.RequestServices
            .GetRequiredService<IViewComponentHelper>();
        ((IViewContextAware)viewComponentHelper).Contextualize(ViewContext);

        // Construct the PageSizeModel (if not provided) with final settings
        var pagerModel = new PageSizeModel
        {
            ViewType = finalViewType,
            UseLocalView = useLocalView,
            UseHtmx = useHtmx,
            PageSizes = pageSizes,
            PagerId = pagerId,
            Model = Model,
            LinkUrl = finalLinkUrl,
            MaxPageSize = maxPageSize,
            PageSize = finalPageSize,
            TotalItems = finalTotalItems
        };

        // Safely invoke the "PageSize" view component
        try
        {
            var result = await viewComponentHelper.InvokeAsync("PageSize", pagerModel);
            output.Content.SetHtmlContent(result);
        }
        catch (Exception ex)
        {
            // Optional: Log or display an error
            output.Content.SetHtmlContent(
                $"<p class=\"text-red-500\">Failed to render PageSize component: {ex.Message}</p>"
            );
        }
    }
```

Ein kniffliges Stück ist der richtige Umgang mit `PageSizeSteps` attribut.- Dies ist eine durch Komma getrennte Liste von Seitengrößen, die der Benutzer auswählen kann..- ODER Sie können in Ihren eigenen Schritten übergeben werden.MSC2-Sie können auch die MaxPageSize angeben, die die maximale Seitengröße ist, die ausgewählt werden kann.

```csharp
    private List<int> CalculatePageSizes(int totalItems, int maxxPageSize, int[] pageSizeSteps)
    {
        var pageSizes = new List<int>();

        // 1. Include all fixed steps up to both TotalItems and MaxPageSize
        foreach (var step in pageSizeSteps)
        {
            if (step <= totalItems && step <= maxxPageSize)
                pageSizes.Add(step);
        }

        // 2. If TotalItems exceeds the largest fixed step, keep doubling
        int lastFixedStep = pageSizeSteps.Last();
        if (totalItems > lastFixedStep)
        {
            int next = lastFixedStep;
            while (next < totalItems && next < maxxPageSize)
            {
                next *= 2; // double the step
                if (next <= totalItems && next <= maxxPageSize)
                {
                    pageSizes.Add(next);
                }
            }
        }

        // 3. Include TotalItems if it's not already in the list
        if (totalItems <= maxxPageSize && !pageSizes.Contains(totalItems))
        {
            pageSizes.Add(totalItems);
        }

        pageSizes.Sort();

        return pageSizes;
    }

```

Wie bei meinem anderen Tag-Helfer verwende ich das TagHelper-->-ViewComponent-Muster, um die Ansichtskomponente zu rendern... Dies ermöglicht es mir, den Tag-Hilfer einfach zu halten und den View-Komponentenkomplex zu verwenden.MSC2. Es erlaubt Ihnen auch Ansichten für verschiedene Erfahrungen zu ändern oder sogar Ihre eigene Ansicht zu injizieren und diese zu benutzen. `use-local-view`).

```csharp

public class PageSizeViewComponent : ViewComponent
    {
        public IViewComponentResult Invoke(PageSizeModel model)
        {
            if(model.Model != null)
            {
                model.LinkUrl ??= model.Model.LinkUrl;
                
                if(model.Model is IPagingSearchModel searchModel)
                {
                    model.SearchTerm ??= searchModel.SearchTerm;
                }
            }
            
            var viewName = "Components/Pager/Default";

            var useLocalView = model.UseLocalView;

            return (useLocalView, model.ViewType) switch
            {
                (true, ViewType.Custom) when ViewExists(viewName) => View(viewName, model),
                (true, ViewType.Custom) when !ViewExists(viewName) => throw new ArgumentException("View not found: " + viewName),
                (false, ViewType.Bootstrap) => View("/Areas/Components/Views/PageSize/BootstrapView.cshtml", model),
                (false, ViewType.Plain) => View("/Areas/Components/Views/PageSize/PlainView.cshtml", model),
                (false, ViewType.TailwindAndDaisy) => View("/Areas/Components/Views/PageSize/Default.cshtml", model),
                _ => View("/Areas/Components/Views/PageSize/Default.cshtml", model)
            };

            // If the view exists in the app, use it; otherwise, use the fallback RCL view
        }
        /// <summary>
        /// Checks if a view exists in the consuming application.
        /// </summary>

        private bool ViewExists(string viewName)
        {
            var result = ViewEngine.FindView(ViewContext, viewName, false);
            return result.Success;
        }
    }
```

# Schlussfolgerung

Gut, dass es für den PageSize Tag Helfer , , . , I' , die immer noch an der Dokumentation und Beispiele arbeiten, aber der Code wird besser täglich gettign , . ,