Search code examples
asp.net-mvc-3paginationhtml-helper

Generating correct Url inside HtmlHelper Method


I have built a crude HTML Helper method with the aim of creating two pagination links (Next and Previous).

Below is the code for the ViewModel (DTO) being passed to the Helper Method:

public class PaginationVM
{
    public int TotalItems { get; set; }
    public int ItemsPerPage { get; set; }
    public int CurrentPage { get; set; }
    public int TotalPages
    {
        get { return (int)Math.Ceiling((decimal)TotalItems / ItemsPerPage); }
    }
}

And here is the code for the HTML Helper Method:

public static MvcHtmlString Paginator(this HtmlHelper htmlHelper, PaginationVM pInfo)
{
    StringBuilder result = new StringBuilder();

    var nextPage = pInfo.CurrentPage + 1;
    var prevPage = pInfo.CurrentPage - 1;

    if (pInfo.CurrentPage < pInfo.TotalPages)
    {
        TagBuilder tagA = new TagBuilder("a");
        tagA.MergeAttribute("href", "page/" + nextPage);
        tagA.InnerHtml = "Prev";
        tagA.AddCssClass("prev");
        result.Append(tagA.ToString());
    }
    else
    {
        TagBuilder tagB = new TagBuilder("a");
        tagB.MergeAttribute("href", "page/" + prevPage);
        tagB.InnerHtml = "Next";
        tagB.AddCssClass("next");
        result.Append(tagB.ToString());
    }

    return MvcHtmlString.Create(result.ToString());
}

The above combination of code seems to work, but only from the homepage, if I click the Prev link [ localhost:xxxx/page/2 ], and go to page 2, then the links become [ localhost:xxxx/page/page/2 ].

My routing looks as follows

routes.MapRoute(
    "PagingPosts/param", // Route name
    "page/{pageId}", // /page/3
    new { controller = "Home", action = "Index", pageId = UrlParameter.Optional }, // Parameter defaults
    new { pageId = @"\d+" } // Constraints: pageId must be numerical
);

As you can see the word page is now duplicated. I can think of a way for fixing this problem, and that is to tell it to generate an ActionLink from the root, and build my URL that way, but I'm not sure how to do that in the HTML helper, and what the best way to do it would be.


Solution

  • A good way is to define a helper that takes a Func<int, string> delegate that can be passed the page number and return the generated URL string. This way, the paginator helper is not bound to any particular routing and can be reused across your application easily.

    Something like

    public static MvcHtmlString Paginator(this HtmlHelper htmlHelper, 
                                          PaginationVM pInfo, 
                                          Func<int, string> pageUrl)
    {
        StringBuilder result = new StringBuilder();
    
        if (pInfo.TotalPages > 1)
        {
            TagBuilder tag;
    
            // previous link
            if (pInfo.CurrentPage > 1)
            {
                tag = new TagBuilder("a");
                tag.MergeAttribute("href", pageUrl(pInfo.CurrentPage - 1));
                tag.AddCssClass("Prev");
                tag.InnerHtml = "previous";
    
                result.AppendLine(tag.ToString());
            }
    
            // numbered links
            for (int i = 1; i <= pInfo.TotalPages; i++)
            {
                if (i == pInfo.CurrentPage)
                {
                    tag = new TagBuilder("span");
                }
                else 
                {
                    tag = new TagBuilder("a");
                    tag.MergeAttribute("href", pageUrl(i));
                }
    
                tag.InnerHtml = i.ToString();
    
                result.AppendLine(tag.ToString());
            }
    
            // next page link
            if (pInfo.CurrentPage < pInfo.TotalPages)
            {
                tag = new TagBuilder("a");
                tag.MergeAttribute("href", pageUrl(pInfo.CurrentPage + 1));
                tag.AddCssClass("Next");
                tag.InnerHtml = "next";
                result.AppendLine(tag.ToString());
            }
        }
    
        return MvcHtmlString.Create(result.ToString());
    }
    

    Now in your view, you would use it like

    @Html.Paginator(pageInfo, pageId => Url.Action("Index", "Home", new { pageId }))