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 Tag helper @(Part {1} bareMS K3Bones})
Tuesday, 11 March 2025
一. 导言导言 导言 导言 导言
前几天需要执行一个工作项目, 实施传呼表格结果@. @ my go-_to 调用标签助手始终是 由Darrel O'Neill提供 正如我所写的 在这里 不论出于何种原因, 停止工作 因此,我决定自己建造一个... ....
和往常一样,你可以得到源头 我的吉他露露,
我有这个项目的样本场地 主办于此
这里有像这样输出的样本@ : @

所需资源所需资源所需资源所需资源 所需经费所需资源所需资源
对于这个标签助手,我有一些要求
- 应该无缝地配合 反尾风 和 DisayUI 调音界面我首选的CSS框架 .
- 使用 HTMX 应该不会造成任何问题
- 如果您使用 HTMX 键仍然有效, 您需要添加一个按钮\ ).\
- 应容易配置和使用
- 接受一个呼叫模型, 所以它“ '” 可以在 Razor 页面中简单使用
- 应该能够配置一些简单的参数
- 应该是小菜一碟 这样你们这些伟大的人就能玩它了
- 应该是两个视图元件和标签帮助器, 因此它可以同时用于 Razor 页面和视图@; @ 。 考虑到它也应该有一个压倒性
Dafault.cshtml视图. - 工作时使用简单的搜索函数@. @%
未来,I'将增加能力到:
- 添加自定义 CSS 以避免被绑到 DaisyUI 和 Tadywind @~~ @ 我附加了此能力, 这里已经看到演示@ :} @ https @://#taghelpersample+.}大多是纯正的 [email protected] @MS K3_Home_/PlainView
- 指定页面大小的能力
- 能够将自定义的 JS调用添加到页面下拉大小@ ( 允许您不使用 HTMX).\
- 使用阿尔卑斯山让呼机更活跃、反应更灵敏, 上一条).
安装装置
标签助手现在是一个闪亮的新 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);
}
它包括这些步骤 :
- 将输出标签名设为
div; 这里是呼叫器的容器 - 删除所提供内容不需要的所有属性@ ( @ butleave all the user provided as\ MS K1}这样可以简单定制@ MPK2
- 如果不提供“ (” , 请将 PagerId 设为随机的 GUID 。 这是用于自定义代码@ MS K1} 您可以指定代号, 或者让此代号来处理它 @ ).
- 如果不提供 @ -}, 请设置当前路径的链接Url 。 如果您想要使用不同的 URL@ MS K1 允许您覆盖此路径
- 设置页面Size @,}Page_,}ViewType,}总项目和搜索术语
IPagingModel并在没有进一步配置的情况下进行呼叫器工作@. @% - 将 ID 属性设定为 falcellrld @ .}
- 从 DI 容器获取 ViewContext@.}查看元件帮助器, 并将其与当前视图contleMSK0 连接到上下文
- 创建新
PagerViewModel属性设置为我们拥有的值或默认值,如果未提供\ . - 启动
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>
}
- 页面缩放大小
- 下个和上一个链接
- 向后跳过和往前跳过链接
- 页面链接
- 页面信息文本
页面大小下调
在最初的标签助手中,我漏掉了一个东西 就是页面大小的下调@. @
这是一个简单选择的列表@ , @% 您可以看到我首先定义 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=.}