Back to "Pagging View Component ASP.NET Core Tag Helper (osa 1.1, tavallaan...Flippy Tag Helper)"

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

Pagging View Component ASP.NET Core Tag Helper (osa 1.1, tavallaan...Flippy Tag Helper)

Monday, 17 March 2025

Johdanto

Joten rakentaessani projektia rakensin alun perin Hakemistoapulainen Kuvaan myös TOISEN tarpeen. Keino rakentaa HtMX-tuella helposti lajittelutoiminto tulostaulukolle.

Esittelen teille Flippy Table Header -tagin apurijutun. Tämä on vain nopea artikkeli, jossa on paljon koodia. Voit aina nähdä näytteet täältä...................................................................................................................................... Ja lähdekoodi kuten aina is tässä.

Olen aika karmea rakennusesimerkki (se on slog innit), mutta yritän lisätä sitä aina vain enemmän, kun lähden mukaan. Asennettava:

dotnet add package mostlylucid.pagingtaghelper

Flippy TagHelper

Flippy Tagin auttaja

Mikä tämä on? Lyhyesti sanottuna se on tapa luoda taulukon otsikko (tai oikeastaan missä tahansa muualla), jonka avulla voit lajitella tulostaulukon.

Yksinkertaisimmillaan (ja ilman HTMX-integraatiota) voit tehdä tämän.

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

Tässä näet pylvään nimen, otsikon testin ja paikan, johon palata (kiitos JetBrains.Huomautukset Joka muun muassa, jonka pintaa olen hädin tuskin raapaissut, antaa tietoja Controller- ja Action-nimille).

Tämä luo linkin, joka lähettää takaisin PageSortTagHelperNoHtmx Euroopan parlamentin ja neuvoston asetus (EU) N:o 1380/2013, annettu 11 päivänä joulukuuta 2013, Euroopan aluekehitysrahastosta (EUVL L 347, 20.12.2013, s. 1). Home ohjain, jossa on sarakkeen nimi ja nykyinen lajijärjestys sekä muut URL-osoitteessa olevat muuttujat (ohjattu auto-append-querystring Ominaisuus. Tämän avulla voit helposti saada hyödyllisen postback-linkin, näet alla Olen tehnyt siitä melko joustavan, jossa voit määrittää href / action & controller ja saada linkin liitteenä olevien kyselyparametrien kanssa.

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

Tämä on menetelmä, joka liittää kyselyparametrit URL-osoitteeseen. Se on aika yksinkertainen, se luo URL-osoitteen, joka perustuu Controller sekä Action attribuutteja tai jos olet asettanut href Se käyttää tätä ominaisuutta. Jos olet asettunut AutoAppend Jos se on väärin, se vain käyttää href Attribuutti sellaisena kuin se on (tarkoittaa, että voit pyörittää omaasi tiettyjä käyttötapauksia varten).

Hullu konfiguraatio

Jotta tästä olisi mahdollisimman paljon hyötyä HTMX:n kanssa / ilman. Kanssa / ilman Tailwind & DaisyUI jne. Olen antanut sinulle BUNCH ominaisuuksia käyttää määrittää tämän suhteellisen yksinkertainen ohjaus

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

Tässä näet, että minulla on ominaisuuksia PRETTTYlle paljon kaikkea hallinnassani (en aio käydä niitä kaikkia läpi täällä, niiden pitäisi olla aika itsestään selviä).

HTMX:llä

Kuten olet ehkä oppinut, olen HTMX NUT, joten kuten tavallista, tämä tukee HTMX:ää melko saumattomasti: KESKUSTELUA use-htmx totta, joka täyttää hx-vals Ominaisuus. Tämän lisäksi käytän HTMX-tagihelpottimet tehdä koodista mahdollisimman yksinkertainen.

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

Ja se siitä sitten tulikin. teoksia HTMX:n kanssa saumattomasti.

MVC-ohjain

Lähetän tämän sitten takaisin yksinkertaiseen MVC-ohjaimeen, joka tuottaa joitakin näytetietoja:

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

Erytropoietiini OrderByField Laajennusmenetelmä

Minulla ja minulla on pakettina pieni laajennusmenetelmä, jossa dataan sovelletaan nimettyä tilauskenttää (tai IQueryable jne.). Koko laajennusmenetelmä on alla. Voit nähdä, että tässä on kolme menetelmää, yksi vahvojen sarakkeiden nimet, yksi IQueryable-sarakkeiden nimet ja yksi IEnumerables.

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

Johtopäätöksenä

Siinäpä se. Se on vain jotain, mitä tarvitsin, joten rakensin sen napakkaan pakettiin.

logo

©2024 Scott Galloway