Back to "ASP.NET 核心标签助手 @(}Part 2,PageSize)"

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

ASP.NET 核心标签助手 @(}Part 2,PageSize)

Tuesday, 25 March 2025

一. 导言导言 导言 导言 导言

作为我与我的广告标签助手一起不断播映的种子的一部分,我现在已经将PageSize 分离出 PageSieze 变成它自己的标签帮手 @.}这是为了使标记帮助器更加灵活,并允许更复杂的传呼假想@.

这些都是工作 -在进行中 -=YTET -伊甸园字幕组 在写作时,但用在你自己的Risk . @ it'}免费,但没有释放质量 IMHO @(#I doñZ'}甚至没有 完整样本 至今为止,

见 在此标记助手的代码.

页面大小标记辅助工具

与我的其他标签助手一样,这是设计用于使用@-case I经常遇到 @;}如何在结果列表中轻松切换页面大小. 乍一看,这似乎相当直截了当 但一旦你把HTMX加入到混音器里 它就会变成TRICKY

所需资源所需资源所需资源所需资源 所需经费所需资源所需资源

因此,在我使用的情况下 要求如下

  1. 允许方便地用于 Teh 开发器 @ ( @ {尽可能多地配置一个简单的页面模型\ MS K1} 。
  2. 支持使用 HTMX @ - @ 意指它@'}%s 能够发送回 保留所有查询字符串参数. 这很重要, 因为它允许用户更改页面大小而不丢失他们应用过的任何其他过滤器@ . @
  3. 具有某种动态的页面大小范围 @ ( 包括“ MS K1” All'\ 选项@ ).}

标签辅助工具

考虑到这一点,我设计了“标签求助者”如下:

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

所以在这种情况下,再次需要我的标准传呼模型 :

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

提供 Page, TotalItems, PageSize, ViewType 和 LinkUrl 用于拨号@. @%

您也可以在模型的属性中以属性的形式指定这些\ :\


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

在哪里 hx-target 这是 HTMX 请求和 hx-swap 响应的目标@. @%

在此情况下, 它使用指定 PageSize 和 TotalItems 和 ViewType 以设定页面大小选择器@. @%

JS Shenanigans( 联合党)

不幸的是,这次我有一个要求 我能够处理纯服务器的一面\ '\ t 也就是我想在页面化更改请求中保存查询字符串参数}.} 为了达到这个目的,我有两个联署材料的两部分 将根据HTMX是否使用

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

如果正在使用 HTMX , 它会将其转换到页面@: @%

(() => {
    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;
})();

这是一个简单的 JavaScript 片段, 它会将当前查询字符串参数输入到请求中@ . @

如果 HTMX 不启用, 则会变成此@ : @ @

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

I'llliall lailly 所做的一个更改是更改“{'}_gang@' 变量(;}) 的名称 __pageSizeListenerAttached 因为它有点过于通用, 我用它来使用 HTMX 和非@-_HTMX 请求 @.}

D. CODE

标签助手本身非常简单@ , @ 主代码只是弹出一些属性, 然后将视图制成元件\ .}

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

一个棘手的片段是正确处理 PageSizeSteps 属性@. 这是用户可以从 . OR中选择的页面大小逗号分隔列表, 您可以在您自己的阶梯里通过. 您也可以指定最大页码大小的 MaxPageSize

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

正如我的其他标签助手一样, 我使用 TagHelfer @ ->View Concomonent 模式来设置视图元件@ MS K1} 允许我保持标记辅助器的简单和查看元件复杂 .}它也允许你改变对不同体验的看法;\ 或甚至输入您自己的观点并使用“ ” 使用的“ IMSK4” use-local-view).


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

在结论中的结论

正在整理文档和示例, 但该代码每天的亮度更好@. @#

logo

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