Back to "A Paging-View-Komponente ASPA.-NET Core Tag Helper MSA(-Part Arta Arta ...-A Flippy Tag Helper MSA"

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

ASP.NET Core PagingTagHelper TagHelper

A Paging-View-Komponente ASPA.-NET Core Tag Helper MSA(-Part Arta Arta ...-A Flippy Tag Helper MSA

Monday, 17 March 2025

Einleitung

Während ich ein Projekt aufbaute, baute ich die Paging Tag Helfer für Ich cam auch über ANOTHER need-Ein Weg, um leicht zu bauen Sortierfunktionalität für eine Tabelle der Ergebnisse mit HtMX-Unterstützung

So..... Ich präsentiere Ihnen die Flippy Tisch Kopf-Tag Helfer thingy. Dies ist nur ein schneller Artikel mit einer LOT von code-. die Proben hierUnd der Quellcode wie immer ist hier.

I'-m ziemlich schreckliche Gebäudebeispiele (-it-'-s ein Slog innit-)- aber IMSC4-ll versuchen, mehr und mehr hinzuzufügen, während ich entlang gehe MSC5- Zum Installieren von :

dotnet add package mostlylucid.pagingtaghelper

Flippy TagHelper

Der Flippy Tag Helfer

Also, was ist dieses Ding??? Nun in Kürze es .'?s eine Möglichkeit, einen Tabellenkopf zu generieren . (... oder irgendwo anders wirklich ..)... was Ihnen erlaubt, eine Tabelle der Ergebnisse zu sortieren .

An ihm lassen Sie das einfachste und ohne HTMX-Integration tun.

    <sortable-header column="@nameof(FakeDataModel.CompanyCity)"
                             current-order-by="@Model.OrderBy"
                             descending="@Model.Descending"
                             controller="Home"
                             use-htmx="false"
                             action="PageSortTagHelperNoHtmx"
                       >@Html.DisplayNameFor(x => x.Data.First().CompanyCity)
</sortable-header>

Hier sehen Sie den Spaltennamen, den Test für den Header und einen Platz zum Zurückgeben von -(. JetBrains-.-Annotationen welche unter vielen anderen Sachen, die I']ve kaum zerkratzten die Oberfläche des Gebens intellisense für den Controller und Aktion Namen

Dies wird einen Link erzeugen, der zurück zu den PageSortTagHelperNoHtmx Maßnahmen im Bereich der Home Controller mit dem Spaltennamen und der aktuellen Sortierreihenfolge sowie allen anderen Parametern in der URL auto-append-querystring Attribute: . , Dies ermöglicht es Ihnen leicht einen nützlichen Postback Link zu erhalten., , Sie sehen unten Ich machte es ziemlich flexibel, wo Sie href , / , Aktion , & , Controller und bekommen einen Link zurück mit den Querystring-Parametern angehängt

    private void AddQueryStringParameters(TagHelperOutput output, bool newDescending)
    {
        string? href = "";

        // If Controller and Action are provided, generate URL dynamically
        if (!string.IsNullOrEmpty(Controller) && !string.IsNullOrEmpty(Action))
        {
            href = Url.ActionLink(Action, Controller);
          
        }
        else if (output.Attributes.ContainsName("href")) // If href is manually set, use it
        {
            href = output.Attributes["href"].Value?.ToString() ?? "";
        }
        if(string.IsNullOrEmpty(href)) throw new ArgumentException("No href was provided or could be generated");
        
        // If AutoAppend is false or href is still empty, don't modify anything
        if (!AutoAppend && !string.IsNullOrWhiteSpace(href))
        {
            output.Attributes.RemoveAll("href");
            output.Attributes.SetAttribute("href", href);
            return;
        }

        // Parse the existing URL to append query parameters
        var queryStringBuilder = QueryString.Empty
            .Add("orderBy", Column)
            .Add("descending", newDescending.ToString().ToLowerInvariant());

        // Preserve existing query parameters from the current request
        foreach (var key in ViewContext.HttpContext.Request.Query.Keys)
        {
            var keyLower = key.ToLowerInvariant();
            if (keyLower != "orderby" && keyLower != "descending") // Avoid duplicating orderBy params
            {
             queryStringBuilder=   queryStringBuilder.Add(key, ViewContext.HttpContext.Request.Query[key]!);
            }
        }
href+= queryStringBuilder.ToString();
        
        // Remove old href and set the new one with the appended parameters
        output.Attributes.RemoveAll("href");
        output.Attributes.SetAttribute("href", href);
    }

Dies ist die Methode, die die Querystring-Parameter an die URL anhängt. Controller und Action Attribute oder wenn Sie die href Attribut, das es verwendet, wenn Sie setzen AutoAppend zu false es wird nur die href Attribut, wie es ( ist Mittel können Sie Ihre eigene für spezifische Verwendung rollen

Verrückte Konfig

Um dies so nützlich wie möglich zu machen, ohne HTMX®.. Mit -/. ohne Tailwind. - &. DaisyUI usw. mit IM'.ve können Sie diese relativ einfache Steuerung konfigurieren.

    [HtmlAttributeName("hx-controller")]
    [AspMvcController] // Enables IntelliSense for controller names
    public string? HXController { get; set; }

    [HtmlAttributeName("hx-action")]
    [AspMvcAction] // Enables IntelliSense for action names
    public string? HXAction { get; set; }
    
    
    [HtmlAttributeName("action")]
    [AspMvcAction]
    public string? Action { get; set; }
    
    [HtmlAttributeName("controller")]
    [AspMvcController]
    public string? Controller { get; set; }
    
    [ViewContext]
    [HtmlAttributeNotBound]
    public ViewContext ViewContext { get; set; }
    /// <summary>
    /// The column to sort by
    /// </summary>

    [HtmlAttributeName("column")] public string Column { get; set; } = string.Empty;

    /// <summary>
    /// Whether to auto-append any query string parameters
    /// </summary>

    [HtmlAttributeName("auto-append-querystring")] public bool AutoAppend { get; set; } = true;
    
    // <summary>
    /// Whether to use htmx ; specifcally used to set hx-vals
    /// </summary>

    [HtmlAttributeName("use-htmx")] public bool UseHtmx { get; set; } = true;

    /// <summary>
    /// The currently set order by column
    /// </summary>

    [HtmlAttributeName("current-order-by")]
    public string? CurrentOrderBy { get; set; }

    /// <summary>
    /// Sort direction, true for descending, false for ascending
    /// </summary>

    [HtmlAttributeName("descending")] public bool Descending { get; set; }

    
    /// <summary>
    ///  CSS class for the chevron up icon
    /// </summary>

    [HtmlAttributeName("chevron-up-class")]
    public string? ChevronUpClass { get; set; }
    
    /// <summary>
    ///  CSS class for the chevron down icon
    /// </summary>


    [HtmlAttributeName("chevron-down-class")]
    public string? ChevronDownClass { get; set; }
    
    /// <summary>
    /// The CSS class for the chevron when unsorted
    /// </summary>

    
    [HtmlAttributeName("chevron-unsorted-class")]
    public string? ChevronUnsortedClass { get; set; }

    /// <summary>
    /// The CSS class to use for the tag.
    /// </summary>

    [HtmlAttributeName("tag-class")] public string? TagClass { get; set; }

Sie können hier sehen, ich habe Eigenschaften für PRETTY MUCH alles in der Steuerung.(-I'-m nicht gehen, um durch sie alle hier zu gehen, sollten sie ziemlich selbst sein...

Mit HTMX

Nun, wie Sie vielleicht gelernt haben I' hm ein HTMX NUT so wie üblich unterstützt diese HTX ziemlich nahtlos Es ENDGÜLT, use-htmx true, die in der hx-vals zusammen mit diesem verwende ich die HTMX-Taghilfer um den Code so einfach wie möglich zu machen

            <sortable-header column="Name"
                             current-order-by="@Model.OrderBy"
                             descending="@Model.Descending"
                             hx-get
                             hx-route-pagesize="@Model.PageSize"
                             hx-route-page="@Model.Page"
                             hx-route-search="@Model.SearchTerm"
                             hx-controller="Home"
                             hx-action="PageSortTagHelper"
                             hx-indicator="#loading-modal"
                             hx-target="#list"
                             hx-push-url="true">@Html.DisplayNameFor(x => x.Data.First().Name)
            </sortable-header>

Und nun, dass ' ist es reallty nur Arbeiten mit HTMX nahtlos

Der MVC-Controller

Ich poste dies dann zurück zu einem einfachen MVC-Controller, der einige Beispieldaten erzeugt.

    [Route("PageSortTagHelper")]
    public async Task<IActionResult> PageSortTagHelper(string? search, int pageSize = 10, int page = 1, string? orderBy = "", bool descending = false)
    {
        var pagingModel = await SortResults(search, pageSize, page, orderBy, descending);

        if (Request.IsHtmxBoosted() || Request.IsHtmx())
        {
            return PartialView("_PageSortTagHelper", pagingModel);
        }
        return View("PageSortTagHelper", pagingModel);
    }
    
     private async Task<OrderedPagingViewModel> SortResults(string? search, int pageSize, int page, string? orderBy, bool descending)
    {
        search = search?.Trim().ToLowerInvariant();
        var fakeModel = await dataFakerService.GenerateData(1000);
        var results = new List<FakeDataModel>();

        if (!string.IsNullOrEmpty(search))
            results = fakeModel.Where(x => x.Name.ToLowerInvariant().Contains(search)
                                           || x.Description.ToLowerInvariant().Contains(search) ||
                                           x.CompanyAddress.ToLowerInvariant().Contains(search)
                                           || x.CompanyEmail.ToLowerInvariant().Contains(search)
                                           || x.CompanyCity.ToLowerInvariant().Contains(search)
                                           || x.CompanyCountry.ToLowerInvariant().Contains(search)
                                           || x.CompanyPhone.ToLowerInvariant().Contains(search)).ToList();
        else
        {
            results = fakeModel.ToList();
        }

        if (!string.IsNullOrWhiteSpace(orderBy))
        {
            results = results.OrderByField(orderBy, descending).ToList();
        }

        var pagingModel = new OrderedPagingViewModel();
        pagingModel.TotalItems = results.Count();
        pagingModel.Page = page;
        pagingModel.SearchTerm = search;
        pagingModel.PageSize = pageSize;
        pagingModel.Data = results.Skip((page - 1) * pageSize).Take(pageSize).ToList();
        pagingModel.OrderBy = orderBy;
        pagingModel.Descending = descending;
        return pagingModel;
    }

Das OrderByField Erweiterungsverfahren

Oh und ich habe eine kleine Erweiterungsmethode verpackt, in der ein benanntes Bestellfeld auf die Daten angewendet wird. IQueryable Sie können sehen, dass dies eine Methode für starke getippte Spaltennamen und eine für IQueryable String-Spaltennamen hat.

using System.Linq.Expressions;
using System.Reflection;

namespace mostlylucid.pagingtaghelper.Extensions;

public static class QueryableExtensions
{
    public static IQueryable<T> OrderByField<T, TKey>(
        this IQueryable<T> source,
        Expression<Func<T, TKey>> keySelector,
        bool descending = false)
    {
        return descending ? source.OrderByDescending(keySelector) : source.OrderBy(keySelector);
    }


    public static IQueryable<T> OrderByField<T>(
        this IQueryable<T> source,
        string sortBy,
        bool descending = false)
    {
        if (string.IsNullOrWhiteSpace(sortBy))
            return source; // No sorting applied

        var property =
            typeof(T).GetProperty(sortBy, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        if (property == null)
            throw new ArgumentException($"Property '{sortBy}' not found on type '{typeof(T)}'.");

        var param = Expression.Parameter(typeof(T), "x");
        var propertyAccess = Expression.MakeMemberAccess(param, property);
        var lambda = Expression.Lambda(propertyAccess, param);

        var methodName = descending ? "OrderByDescending" : "OrderBy";

        var resultExpression = Expression.Call(
            typeof(Queryable),
            methodName,
            new[] { typeof(T), property.PropertyType },
            source.Expression,
            Expression.Quote(lambda)
        );

        return source.Provider.CreateQuery<T>(resultExpression);
    }
    
    public static IEnumerable<T> OrderByField<T>(
        this IEnumerable<T> source,
        string sortBy,
        bool descending = false)
    {
        var property = typeof(T).GetProperty(sortPropertyName(sortBy), BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        if (property == null)
            throw new ArgumentException($"Property '{sortBy}' not found on type '{typeof(T)}'.");

        return descending
            ? source.OrderByDescending(x => property.GetValue(x, null))
            : source.OrderBy(x => property.GetValue(x, null));
    }

    // Helper methods for readability (optional)
    private static string sortPropertyName(string sortBy) => sortBy.Trim();
    private static string methodName(bool descending) => descending ? "OrderByDescending" : "OrderBy";
}

Schlussfolgerung

Dass-''s wirklich es -.'s gerade etwas, das ich brauchte, also baute ich es ein stecken im Nuget Paket -MSC3's

logo

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