ASP.NET Core Tag helper @(Part {1} bareMS K3Bones}) (中文 (Chinese Simplified))

ASP.NET Core Tag helper @(Part {1} bareMS K3Bones})

Tuesday, 11 March 2025

//

10 minute read

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

前几天需要执行一个工作项目, 实施传呼表格结果@. @ my go-_to 调用标签助手始终是 由Darrel O'Neill提供 正如我所写的 在这里 不论出于何种原因, 停止工作 因此,我决定自己建造一个... ....

和往常一样,你可以得到源头 我的吉他露露,

我有这个项目的样本场地 主办于此

这里有像这样输出的样本@ : @

用标签助手搜索演示g

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

对于这个标签助手,我有一些要求

  1. 应该无缝地配合 反尾风 和 DisayUI 调音界面我首选的CSS框架 .
  2. 使用 HTMX 应该不会造成任何问题
  3. 如果您使用 HTMX 键仍然有效, 您需要添加一个按钮\ ).\
  4. 应容易配置和使用
    1. 接受一个呼叫模型, 所以它“ '” 可以在 Razor 页面中简单使用
    2. 应该能够配置一些简单的参数
  5. 应该是小菜一碟 这样你们这些伟大的人就能玩它了
  6. 应该是两个视图元件和标签帮助器, 因此它可以同时用于 Razor 页面和视图@; @ 。 考虑到它也应该有一个压倒性 Dafault.cshtml 视图.
  7. 工作时使用简单的搜索函数@. @%

未来,I'将增加能力到:

  1. 添加自定义 CSS 以避免被绑到 DaisyUI 和 Tadywind @~~ @ 我附加了此能力, 这里已经看到演示@ :} @ https @://#taghelpersample+.}大多是纯正的 [email protected] @MS K3_Home_/PlainView
  2. 指定页面大小的能力
  3. 能够将自定义的 JS调用添加到页面下拉大小@ ( 允许您不使用 HTMX).\
  4. 使用阿尔卑斯山让呼机更活跃、反应更灵敏, 上一条).

安装装置

标签助手现在是一个闪亮的新 Giget 软件包, 这样您就可以安装它 。

dotnet add package mostlylucid.pagingtaghelper

您% 'd 然后添加标签助手到您的 _ViewImports.cshtml 类似文件@ @ : @

@addTagHelper *, mostlylucid.pagingtaghelper

然后您就可以开始使用它@; @I 提供一些辅助课程, 您可以用来配置这些类

IPagingModel

这是您需要启动的“ .” It'}这是一个简单的界面, 您可以在您的模型上执行此接口来获取 pageing\ MS K4}注意 ViewType 默认为 TailwindANdDaisy 但您可以设置为 Custom, Plain 或 Bootstrap 如果您想要使用不同的视图 .}

public enum ViewType
{
TailwindANdDaisy,
Custom,
Plain,
Bootstrap
}

OR 您甚至可以使用 TagHelfer @'s 指定自定义视图 use-local-view 属性@. @%

namespace mostlylucid.pagingtaghelper.Models;

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; }
}
namespace mostlylucid.pagingtaghelper.Models;

public interface IPagingSearchModel : IPagingModel
{
    public string? SearchTerm { get; set; }
}

我还在项目中执行了这些要求,以提供一个基线 :

public abstract class BasePagerModel : IPagingModel
{
    public int Page { get; set; } = 1;
    public int TotalItems { get; set; } = 0;
    public int PageSize { get; set; } = 10;
    public ViewType ViewType { get; set; } = ViewType.TailwindANdDaisy;

    public string LinkUrl { get; set; } = "";

}
public abstract class BasePagerSearchMdodel : BasePagerModel, IPagingSearchModel
{
    public string? SearchTerm { get; set; }
}

I'Lall 将覆盖未来文章中的搜索功能@..

标签帮助者

然后我就想出了我想让标签求助者 看起来像在使用中的样子

<paging
    x-ref="pager"
    hx-boost="true"
    hx-indicator="#loading-modal"
    hx-target="#user-list "
    hx-swap="show:none"
    model="Model"
    pages-to-display="10"
    hx-headers='{"pagerequest": "true"}'>
</paging>

您可以在这里看到我设置了几个 HTMX 参数和用于 pageing @ .} 我还可以设定要显示的页数和信头数量, 并按请求 *\ MS K1} 这将允许我使用 HTM X 来填充页面@ ).}

组件中还有一个包含其他配置元素的 BUNCH, I'Lll在今后的文章\MS K1中工作。

<paging css-class=""
        first-last-navigation=""
        first-page-text=""
        next-page-aria-label=""
        next-page-text=""
        page=""
        pages-to-display=""
        page-size=""
        previous-page-text=""
        search-term=""
        skip-forward-back-navigation=""
        skip-back-text=""
        skip-forward-text="true"
        total-items=""
        link-url=""
        last-page-text=""
        show-pagesize=""
        use-htmx=""
        use-local-view=""
        view-type="Bootstrap"
        htmx-target=""
        id=""
></paging>

TagHelfer 相当简单, 但有一大堆属性使用户能够自定义行为@( @% 您可以在下面看到这一点 视图视图视图 “)”除了属性“( ”之外,我在这里为简洁而赢得的“MS K2”粘贴。代码相当直截了当地“:”

    /// <summary>
    /// Processes the tag helper to generate the pagination component.
    /// </summary>

    /// <param name="context">The tag helper context.</param>
    /// <param name="output">The tag helper output.</param>
    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
   
        output.TagName = "div";
        
        //Remove all the properties that are not needed for the rendered content.
        output.Attributes.RemoveAll("page");
        output.Attributes.RemoveAll("link-url");
        output.Attributes.RemoveAll("page-size");
        output.Attributes.RemoveAll("total-items");
        output.Attributes.RemoveAll("pages-to-display");
        output.Attributes.RemoveAll("css-class");
        output.Attributes.RemoveAll("first-page-text");
        output.Attributes.RemoveAll("previous-page-text");
        output.Attributes.RemoveAll("skip-back-text");
        output.Attributes.RemoveAll("skip-forward-text");
        output.Attributes.RemoveAll("next-page-text");
        output.Attributes.RemoveAll("next-page-aria-label");
        output.Attributes.RemoveAll("last-page-text");
        output.Attributes.RemoveAll("first-last-navigation");
        output.Attributes.RemoveAll("skip-forward-back-navigation");
        output.Attributes.RemoveAll("model");
        output.Attributes.RemoveAll("show-pagesize");
        output.Attributes.RemoveAll("pagingmodel");
        output.Attributes.RemoveAll("use-local-view");
        
        var pagerId =  PagerId ?? $"pager-{Guid.NewGuid():N}";
        var linkUrl = LinkUrl ?? ViewContext.HttpContext.Request.Path;
        PageSize = Model?.PageSize ?? PageSize ?? 10;
        Page = Model?.Page ?? Page ?? 1;
        ViewType = Model?.ViewType ?? ViewType;
        TotalItems = Model?.TotalItems ?? TotalItems ?? 0;
        if(Model is IPagingSearchModel searchModel)
            SearchTerm = searchModel?.SearchTerm ?? SearchTerm ?? "";
        output.Attributes.SetAttribute("id", pagerId);
        var viewComponentHelper = (IViewComponentHelper)ViewContext.HttpContext.RequestServices.GetService(typeof(IViewComponentHelper))!;
        ((IViewContextAware)viewComponentHelper).Contextualize(ViewContext);

        var pagerModel = PagerModel ?? new PagerViewModel()
        {
            
            ViewType = ViewType,
            UseLocalView = UseLocalView,
            UseHtmx = UseHtmx,
            PagerId = pagerId,
            SearchTerm = SearchTerm,
            ShowPageSize = ShowPageSize,
            Model = Model,
            LinkUrl = linkUrl,
            Page = Page,
            PageSize = PageSize,
            TotalItems = TotalItems,
            PagesToDisplay = PagesToDisplay,
            CssClass = CssClass,
            FirstPageText = FirstPageText,
            PreviousPageText = PreviousPageText,
            SkipBackText = SkipBackText,
            SkipForwardText = SkipForwardText,
            NextPageText = NextPageText,
            NextPageAriaLabel = NextPageAriaLabel,
            LastPageText = LastPageText,
            FirstLastNavigation = FirstLastNavigation,
            SkipForwardBackNavigation = SkipForwardBackNavigation,
            HtmxTarget = HtmxTarget,
            
        };

        var result = await viewComponentHelper.InvokeAsync("Pager", pagerModel);
        output.Content.SetHtmlContent(result);
    }

它包括这些步骤 :

  1. 将输出标签名设为 div; 这里是呼叫器的容器
  2. 删除所提供内容不需要的所有属性@ ( @ butleave all the user provided as\ MS K1}这样可以简单定制@ MPK2
  3. 如果不提供“ (” , 请将 PagerId 设为随机的 GUID 。 这是用于自定义代码@ MS K1} 您可以指定代号, 或者让此代号来处理它 @ ).
  4. 如果不提供 @ -}, 请设置当前路径的链接Url 。 如果您想要使用不同的 URL@ MS K1 允许您覆盖此路径
  5. 设置页面Size @,}Page_,}ViewType,}总项目和搜索术语 IPagingModel 并在没有进一步配置的情况下进行呼叫器工作@. @%
  6. 将 ID 属性设定为 falcellrld @ .}
  7. 从 DI 容器获取 ViewContext@.}查看元件帮助器, 并将其与当前视图contleMSK0 连接到上下文
  8. 创建新 PagerViewModel 属性设置为我们拥有的值或默认值,如果未提供\ .
  9. 启动 Pager 查看 PagerViewModel 并设置输出内容到结果@. @%

再一次简单明了

视图功能

视图视图

View Concomonent 的视图非常简单@; @ it'}只是浏览页面和链接,

Complete source code for the Default TailwindUI & Daisy view
@model mostlylucid.pagingtaghelper.Components.PagerViewModel
@{
    var totalPages = (int)Math.Ceiling((double)Model.TotalItems! / (double)Model.PageSize!);
    var pageSizes = new List<int>();
    if (Model.ShowPageSize)
    {
        // Build a dynamic list of page sizes.

        // Fixed steps as a starting point.
        int[] fixedSteps = { 10, 25, 50, 75, 100, 125, 150, 200, 250, 500, 1000 };

        // Add only those fixed steps that are less than or equal to TotalItems.
        foreach (var step in fixedSteps)
        {
            if (step <= Model.TotalItems)
            {
                pageSizes.Add(step);
            }
        }

        // If TotalItems is greater than the largest fixed step,
        // add additional steps by doubling until reaching TotalItems.
        if (Model.TotalItems > fixedSteps.Last())
        {
            int next = fixedSteps.Last();
            while (next < Model.TotalItems)
            {
                next *= 2;
                // Only add if it doesn't exceed TotalItems.
                if (next < Model.TotalItems)
                {
                    pageSizes.Add(next);
                }
            }

            // Always include the actual TotalItems as the maximum option.
            if (!pageSizes.Contains(Model.TotalItems.Value))
            {
                pageSizes.Add(Model.TotalItems.Value);
            }
        }
    }
}
@if (totalPages > 1)
{
    <div class="@Model.CssClass flex items-center justify-center" id="pager-container">
        @if (Model.ShowPageSize)
        {
            var pagerId = Model.PagerId;
            var htmxAttributes = Model.UseHtmx
                ? $"hx-get=\"{Model.LinkUrl}\" hx-trigger=\"change\" hx-include=\"#{pagerId} [name='page'], #{pagerId} [name='search']\" hx-push-url=\"true\""
                : "";


            <!-- Preserve current page -->
            <input type="hidden" name="page" value="@Model.Page"/>
            <input type="hidden" name="search" value="@Model.SearchTerm"/>
            <input type="hidden" class="useHtmx" value="@Model.UseHtmx.ToString().ToLowerInvariant()"/>
            if (!Model.UseHtmx)
            {
                <input type="hidden" class="linkUrl" value="@Model.LinkUrl"/>
            }

            <!-- Page size select with label -->
            <div class="flex items-center mr-8">
                <label for="pageSize-@pagerId" class="text-sm text-gray-600 mr-2">Page size:</label>
                <select id="pageSize-@pagerId"
                        name="pageSize"
                        class="border rounded select select-primary select-sm pt-0 mt-0 min-w-[80px] pr-4"
                        @Html.Raw(htmxAttributes)>
                    @foreach (var option in pageSizes.ToList())
                    {
                        var optionString = option.ToString();
                        if (option == Model.PageSize)
                        {
                            <option value="@optionString" selected="selected">@optionString</option>
                        }
                        else
                        {
                            <option value="@optionString">@optionString</option>
                        }
                    }
                </select>
            </div>
        }

        @* "First" page link *@
        @if (Model.FirstLastNavigation && Model.Page > 1)
        {
            var href = $"{Model.LinkUrl}?page=1&pageSize={Model.PageSize}";
            if (!string.IsNullOrEmpty(Model.SearchTerm))
            {
                href += $"&search={Model.SearchTerm}";
            }

            <a class="btn btn-sm"
               href="@href">
                @Model.FirstPageText
            </a>
        }

        @* "Previous" page link *@
        @if (Model.Page > 1)
        {
            var href = $"{Model.LinkUrl}?page={Model.Page - 1}&pageSize={Model.PageSize}";
            if (!string.IsNullOrEmpty(Model.SearchTerm))
            {
                href += $"&search={Model.SearchTerm}";
            }

            <a class="btn btn-sm"
               href="@href">
                @Model.PreviousPageText
            </a>
        }

        @* Optional skip back indicator *@
        @if (Model.SkipForwardBackNavigation && Model.Page > Model.PagesToDisplay)
        {
            <a class="btn btn-sm btn-disabled">
                @Model.SkipBackText
            </a>
        }

        @* Determine visible page range *@
        @{
            int halfDisplay = Model.PagesToDisplay / 2;
            int startPage = Math.Max(1, Model.Page.Value - halfDisplay);
            int endPage = Math.Min(totalPages, startPage + Model.PagesToDisplay - 1);
            startPage = Math.Max(1, endPage - Model.PagesToDisplay + 1);
        }
        @for (int i = startPage; i <= endPage; i++)
        {
            var href = $"{Model.LinkUrl}?page={i}&pageSize={Model.PageSize}";
            if (!string.IsNullOrEmpty(Model.SearchTerm))
            {
                href += $"&search={Model.SearchTerm}";
            }

            <a data-page="@i" class="btn btn-sm mr-2 @(i == Model.Page ? "btn-active" : "")"
               href="@href">
                @i
            </a>
        }

        @* Optional skip forward indicator *@
        @if (Model.SkipForwardBackNavigation && Model.Page < totalPages - Model.PagesToDisplay + 1)
        {
            <a class="btn btn-sm btn-disabled mr-2">
                @Model.SkipForwardText
            </a>
        }

        @* "Next" page link *@
        @if (Model.Page < totalPages)
        {
            var href = $"{Model.LinkUrl}?page={Model.Page + 1}&pageSize={Model.PageSize}";
            if (!string.IsNullOrEmpty(Model.SearchTerm))
            {
                href += $"&search={Model.SearchTerm}";
            }

            <a class="btn btn-sm mr-2"
               href="@href"
               aria-label="@Model.NextPageAriaLabel">
                @Model.NextPageText
            </a>
        }

        @* "Last" page link *@
        @if (Model.FirstLastNavigation && Model.Page < totalPages)
        {
            var href = $"{Model.LinkUrl}?page={totalPages}&pageSize={Model.PageSize}";
            if (!string.IsNullOrEmpty(Model.SearchTerm))
            {
                href += $"&search={Model.SearchTerm}";
            }

            <a class="btn btn-sm"
               href="@href">
                @Model.LastPageText
            </a>
        }

        <!-- Page info text with left margin for separation -->
        <div class="text-sm text-neutral-500 ml-8">
            Page @Model.Page of @totalPages (Total items: @Model.TotalItems)
        </div>
    </div>
}
这分为几节 :
  1. 页面缩放大小
  2. 下个和上一个链接
  3. 向后跳过和往前跳过链接
  4. 页面链接
  5. 页面信息文本

页面大小下调

在最初的标签助手中,我漏掉了一个东西 就是页面大小的下调@. @ 这是一个简单选择的列表@ , @% 您可以看到我首先定义 fixedSteps 仅是用于下载的固定步骤@. @ 我翻阅这些步骤并将其添加到列表中 .}我一向习惯于有一个“ MS K2” all'}选项,所以如果它不是已经存在的话, 我会在列表中加入全部项目。 <% k4}

@{
    var totalPages = (int)Math.Ceiling((double)Model.TotalItems! / (double)Model.PageSize!);
    var pageSizes = new List<int>();
    if (Model.ShowPageSize)
    {
        // Build a dynamic list of page sizes.

        // Fixed steps as a starting point.
        int[] fixedSteps = { 10, 25, 50, 75, 100, 125, 150, 200, 250, 500, 1000 };

        // Add only those fixed steps that are less than or equal to TotalItems.
        foreach (var step in fixedSteps)
        {
            if (step <= Model.TotalItems)
            {
                pageSizes.Add(step);
            }
        }

        // If TotalItems is greater than the largest fixed step,
        // add additional steps by doubling until reaching TotalItems.
        if (Model.TotalItems > fixedSteps.Last())
        {
            int next = fixedSteps.Last();
            while (next < Model.TotalItems)
            {
                next *= 2;
                // Only add if it doesn't exceed TotalItems.
                if (next < Model.TotalItems)
                {
                    pageSizes.Add(next);
                }
            }

            // Always include the actual TotalItems as the maximum option.
            if (!pageSizes.Contains(Model.TotalItems.Value))
            {
                pageSizes.Add(Model.TotalItems.Value);
            }
        }
    }
}

然后,我把这个写到页面上

  @if (Model.ShowPageSize)
        {
            var pagerId = Model.PagerId;
            var htmxAttributes = Model.UseHtmx
                ? $"hx-get=\"{Model.LinkUrl}\" hx-trigger=\"change\" hx-include=\"#{pagerId} [name='page'], #{pagerId} [name='search']\" hx-push-url=\"true\""
                : "";


            <!-- Preserve current page -->
            <input type="hidden" name="page" value="@Model.Page"/>
            <input type="hidden" name="search" value="@Model.SearchTerm"/>
            <input type="hidden" class="useHtmx" value="@Model.UseHtmx.ToString().ToLowerInvariant()"/>
            if (!Model.UseHtmx)
            {
                <input type="hidden" class="linkUrl" value="@Model.LinkUrl"/>
            }

            <!-- Page size select with label -->
            <div class="flex items-center mr-8">
                <label for="pageSize-@pagerId" class="text-sm text-gray-600 mr-2">Page size:</label>
                <select id="pageSize-@pagerId"
                        name="pageSize"
                        class="border rounded select select-primary select-sm pt-0 mt-0 min-w-[80px] pr-4"
                        @Html.Raw(htmxAttributes)>
                    @foreach (var option in pageSizes.ToList())
                    {
                        var optionString = option.ToString();
                        if (option == Model.PageSize)
                        {
                            <option value="@optionString" selected="selected">@optionString</option>
                        }
                        else
                        {
                            <option value="@optionString">@optionString</option>
                        }
                    }
                </select>
            </div>
        }

您可以看到我可选择使用一些 HTMX 属性将页面大小传送到服务器并更新页面,同时保留当前页数和搜索参数@( @ if any@ ).}

此外,如果您怀疑 use-htmx=false 作为标签助手的参数, 它赢得了@ ' @ t 输出这些标记, 但将允许您使用一些 JUS I 作为 HTML 辅助器来更新页面大小@ MPK1

@Html.PageSizeOnchangeSnippet()
    

这是一个简单的脚本, 它会更新页面大小并重新加载 {(} 页面@ mote this dous ' <% t> 但仍能为纯 CSS {/ _ Bootsstrap 工作, 因为我需要解决属性名称等).}

document.addEventListener("DOMContentLoaded", function () {
    document.body.addEventListener("change", function (event) {
        const selectElement = event.target.closest("#pager-container select[name='pageSize']");
        if (!selectElement) return;

        const pagerContainer = selectElement.closest("#pager-container");
        const useHtmxInput = pagerContainer.querySelector("input.useHtmx");
        const useHtmx = useHtmxInput ? useHtmxInput.value === "true" : true; // default to true

        if (!useHtmx) {
            const pageInput = pagerContainer.querySelector("[name='page']");
            const searchInput = pagerContainer.querySelector("[name='search']");

            const page = pageInput ? pageInput.value : "1";
            const search = searchInput ? searchInput.value : "";
            const pageSize = selectElement.value;
            const linkUrl =  pagerContainer.querySelector("input.linkUrl").value ?? "";
            
            const url = new URL(linkUrl, window.location.origin);
            url.searchParams.set("page", page);
            url.searchParams.set("pageSize", pageSize);

            if (search) {
                url.searchParams.set("search", search);
            }

            window.location.href = url.toString();
        }
    });
});

HTMX一体化

HTMX整合非常简单,因为HTMX是儿童元素的级联。 我们可以确定父元素上的HTMAX参数, 并且这些参数将被继承@.

  • hx-boost="true" - 此使用 nifty hx-}启动功能 以截取点击事件并通过 HTMX. 发送请求
  • hx-indicator="#loadingMS K2modalMSQ}這將顯示一個載荷模式, 當您正在處理請求時會顯示@.
  • hx @ -}目标@="# @user%K2Lost @"}{-这是响应中要交换的元素。 在此情况下, 用户列表 <,将转换为% MS K5}(note_ .:}这包括简单化的Pappler};}您可以用阿尔卑斯语来使这个功能更加活跃 前一条 )}但这次超出范围了
  • hx-swap ="showMS K2none}"

装入模式

这很简单,使用 DaisyUI, 和 尾风来创建简单的装货模式\ .

<div id="loading-modal" class="modal htmx-indicator">
    <div class="modal-box flex flex-col items-center justify-center">
        <h2 class="text-lg font-semibold">Loading...</h2>
        <i class="bx bx-loader bx-spin text-3xl mt-2"></i>
    </div>
</div>

hx-indicator ="#loadingMS K2modal}"}然后指定,当执行 HTMX 请求时,它会被拖动显示 然后隐藏此模式@.

未来特点

因此,“'s part {1,(这里)〞's 显然要覆盖更多LOT。 我将在将来的文章中写下#;包括样板站点@, 替代视图_,}搜索功能和自定义 CSS=.}

Finding related posts...
logo

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