Un outil d'aide à l'affichage des balises ASP.NET Core Tag Helper M SK1Part 1.1, sorta sorta...A Flippy Tag HelpersMSC4 (Français (French))

Un outil d'aide à l'affichage des balises ASP.NET Core Tag Helper M SK1Part 1.1, sorta sorta...A Flippy Tag HelpersMSC4

Monday, 17 March 2025

//

8 minute read

Introduction

Alors alors que je faisais un projet, j’ai d’abord construit Aideur à l'affichage des étiquettes pour I also cam across ANOTHER need. A way to easily build sorting functionality for a table of results with HtMX support

Alors...Je vous présente le Flippy Table Header tag helper thingy. Ce n'est qu'un bref article avec un LOT de code. Vous pouvez toujours voir les échantillons ici. Et le code source comme toujours est ici.

I'j’ai des exemples de construction assez horribles (it'is un embarras innitM SK3 mais jeMSC4essaierai d’ajouter plus et plus à mesure que j’avance Pour installer:

dotnet add package mostlylucid.pagingtaghelper

Aide-étiquette Flippy

L'aide à l'étiquette Flippy

Donc, ce qu’est-ce que c’est?

Il est le plus simple ( et sans l’intégration de HTMX ) il vous permet d’effectuer ce qui suit

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

Vous pouvez ici voir que vous spécifiez le nom de la colonne, le test pour l'en-tête et un endroit à afficher (dans JetBrains.Notes qui, parmi de nombreuses autres choses, que je ' j’ai à peine scratché la surface de donner intellisense pour les noms de contrôleur et d’action).

Cela génèrera un lien qui sera affiché à la page suivante : PageSortTagHelperNoHtmx d'action sur le Home contrôleur avec le nom de la colonne et l'ordre du sort actuel ainsi que tout autre paramètre dans l'URL (contrôlé par auto-append-querystring attribute. Cela vous permet d'obtenir facilement un lien de retour utile, vousM SK2voir ci-dessous J'ai fait en sorte qu'il soit assez souple où vous pouvez spécifier href MSC3 action & contrôleur et obtenir un lien en retour avec les paramètres de la chaîne de demande ajoutées.

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

Il s’agit de la méthode qui ajoute les paramètres des chaînes de demande à l’URL. Controller et Action les attributs ou si vous ' avez href attribute il utilisera que. Si vous'vous avez établi AutoAppend pour fausse, il n’est qu’à utiliser href attribut tel que (meanign vous pouvez enrouler votre propre pour un usage spécifiqueM SK1cases).

Config Crazy

Pour que ce soit le plus utile possible avec / sans HTMX. avec ♫/ sans Tailwind ♫ & DaisyUI etc. ♫I'\je vous donne une BUNCH de propriétés à utiliser pour configurer ce contrôle relativement simple

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

Vous pouvez voir ici que j'ai des propriétés pour PRETTY MUCH tout dans le contrôle (IM SK1je ne vais pas les passer tous ici ils devraient être assez autonomes-explanatoireMSC3

Avec HTMX

Maintenant, comme vous l’avez peut-être appris I'm un HTMX NUT de sorte que comme d’habitude ce supporte HT MX assez soudainement: Il FAULTE à use-htmx true qui remplit le hx-vals attribute. En plus de cela, j'utilise le Aides à l'étiquetage HTMX pour rendre le code aussi simple que possible.

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

Et bien que ce n’est pas le cas ouvrages avec HTMX sans fil.

Le Contrôleur MVC

J’affirme ensuite ce message à un simple Contrôleur MVC qui génère quelques données d’échantillonnage:

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

L'article 5 de la Loi sur l'immigration et le statut des réfugiés OrderByField Méthode d'extension

Oh et j'ai une petite méthode d'extension emballée dans laquelle applique un champ de commande nommé aux données (ou IQueryable etc). T|l'ensemble de la méthode d'extension est ci-dessous . Vous pouvez voir qu'il y a M SK3 des méthodesMSC4 une pour les noms de colonnes fortement écritesMNK5 l'une pour le nom de colonne IQueryable et l'autre pour IEnumerablesMRK6

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

En conclusion

Ce n’est qu’une chose que j’avais besoin, donc je l’ai construite et je la ai fixée dans le paquet nuget.

Finding related posts...
logo

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