模型绑定与分页TagHelper:PageLinkTagHelper.cs相关技术咨询
Hey there! Let's break down your PageLinkTagHelper code and walk through what it does, plus some key improvements and missing pieces to make it production-ready for your model binding and pagination workflow.
Your snippet lays the foundation for a reusable pagination TagHelper—here's a breakdown of each component:
- Target Element Binding: The
[HtmlTargetElement("div", Attributes = "page-model")]attribute tells ASP.NET Core that this TagHelper will automatically process any<div>with apage-modelattribute in your Razor views. - URL Generation Setup: Injecting
IUrlHelperFactoryis the right call—it lets you generate route-based URLs dynamically, which is way more maintainable than hardcoding links. - View Context Access: The
[ViewContext]and[HtmlAttributeNotBound]attributes let the TagHelper access the current view's context (like route data, request details) without exposing this property as an HTML attribute. - Paging Data Carrier: The
PageModelproperty (backed by your customPagingInfoclass) is designed to pass core pagination data—think current page number, total pages, total records—from your view model to the TagHelper. - Action Specification:
PageActionlets you define which controller action the pagination links should point to; we'll expand on this to handle defaults later.
Process Method Your snippet cuts off before the most critical part of any TagHelper: the Process method, which generates the actual HTML output. Here's a complete, production-ready implementation to fill that gap:
public override void Process(TagHelperContext context, TagHelperOutput output) { base.Process(context, output); // Guard against null or invalid pagination data if (PageModel == null || PageModel.TotalPages <= 1) { output.SuppressOutput(); return; } var urlHelper = urlHelperFactory.GetUrlHelper(ViewContext); var paginationLinks = new List<TagHelperContent>(); // Generate "Previous" link if (PageModel.CurrentPage > 1) { paginationLinks.Add(CreatePageLink(urlHelper, PageModel.CurrentPage - 1, "Previous")); } // Generate page number links (with smart range for large datasets) int startPage = Math.Max(1, PageModel.CurrentPage - 2); int endPage = Math.Min(PageModel.TotalPages, PageModel.CurrentPage + 2); // Add first page and ellipsis if needed if (startPage > 1) { paginationLinks.Add(CreatePageLink(urlHelper, 1, "1")); if (startPage > 2) { paginationLinks.Add(CreateEllipsisSpan()); } } // Generate middle page links for (int i = startPage; i <= endPage; i++) { var pageLink = CreatePageLink(urlHelper, i, i.ToString()); // Highlight current page with active class if (i == PageModel.CurrentPage) { var activeWrapper = new TagBuilder("span"); activeWrapper.AddCssClass("active page-link"); activeWrapper.InnerHtml.Append(i.ToString()); var content = new DefaultTagHelperContent(); content.AppendHtml(activeWrapper); paginationLinks.Add(content); } else { paginationLinks.Add(pageLink); } } // Add last page and ellipsis if needed if (endPage < PageModel.TotalPages) { if (endPage < PageModel.TotalPages - 1) { paginationLinks.Add(CreateEllipsisSpan()); } paginationLinks.Add(CreatePageLink(urlHelper, PageModel.TotalPages, PageModel.TotalPages.ToString())); } // Generate "Next" link if (PageModel.CurrentPage < PageModel.TotalPages) { paginationLinks.Add(CreatePageLink(urlHelper, PageModel.CurrentPage + 1, "Next")); } // Wrap links in a Bootstrap-compatible ul container var paginationContainer = new TagBuilder("ul"); paginationContainer.AddCssClass(PaginationClass ?? "pagination"); foreach (var link in paginationLinks) { var listItem = new TagBuilder("li"); listItem.AddCssClass(PageItemClass ?? "page-item"); listItem.InnerHtml.AppendHtml(link); paginationContainer.InnerHtml.AppendHtml(listItem); } output.Content.AppendHtml(paginationContainer); } private TagHelperContent CreatePageLink(IUrlHelper urlHelper, int pageNumber, string linkText) { // Preserve existing route/query parameters (e.g., search filters) var routeValues = new RouteValueDictionary(ViewContext.RouteData.Values); foreach (var queryParam in ViewContext.HttpContext.Request.Query) { if (!routeValues.ContainsKey(queryParam.Key)) { routeValues.Add(queryParam.Key, queryParam.Value); } } routeValues["page"] = pageNumber; var link = new TagBuilder("a"); link.AddCssClass(PageLinkClass ?? "page-link"); link.Attributes["href"] = urlHelper.Action( PageAction ?? ViewContext.RouteData.Values["action"].ToString(), PageController ?? ViewContext.RouteData.Values["controller"].ToString(), routeValues ); link.InnerHtml.Append(linkText); var content = new DefaultTagHelperContent(); content.AppendHtml(link); return content; } private TagHelperContent CreateEllipsisSpan() { var ellipsis = new TagBuilder("span"); ellipsis.AddCssClass("page-link"); ellipsis.InnerHtml.Append("..."); var content = new DefaultTagHelperContent(); content.AppendHtml(ellipsis); return content; }
To make this TagHelper even more flexible and robust, add these enhancements:
a. Add Controller & Custom CSS Class Support
Add properties to let users specify target controllers and custom styles without modifying the TagHelper code:
public string PageController { get; set; } [HtmlAttributeName("pagination-class")] public string PaginationClass { get; set; } [HtmlAttributeName("page-item-class")] public string PageItemClass { get; set; } [HtmlAttributeName("page-link-class")] public string PageLinkClass { get; set; }
b. Preserve Query/Route Parameters
The CreatePageLink method above already handles this—when users filter data (e.g., search for a product), the pagination links will retain those filters so users don't lose their context.
c. Smart Page Range
Instead of showing every page number (which gets messy for large datasets), the code only shows the current page plus/minus 2 pages, with ellipses and first/last page links for navigation.
d. Null Safety & Validation
The Process method includes guards to suppress output if there's only one page or if PageModel is null, preventing broken UI or exceptions.
Here's how you'd use this TagHelper in a view with your model-bound pagination data:
<!-- Assuming your view model has a PagingInfo property --> <div page-model="@Model.PagingInfo" page-action="Index" page-controller="Products" pagination-class="custom-pagination"> <!-- TagHelper replaces this content with pagination links --> </div>
内容的提问来源于stack exchange,提问作者coolhand

