3

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.

1 Answer 1

4

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 }))
Sign up to request clarification or add additional context in comments.

9 Comments

+1, I was just about to post an answer that looked almost identical to this. The only thing I would mention in addition is how to make the URL's look like /Page2 VS /?page=2 with the addition of a new route.
@Jesse - yep, how the routing is defined is a whole other kettle of fish :)
Thank You for the quick reply Russ Cam. Two things, can you please briefly explain how [ Fun<int, string> ] works ? I looked at Microsoft's documentation for Delegate, but it is so complex to understand. :( Also I've copied your code and now my links look like this [localhost:xxxx/?page=2] instead of this [localhost:xxxx/page/2] ! My routing is setup like this.
@Ciwan - Func<int, string> is like a method signature for a method that takes an int and returns a string. In the usage example above, we pass a lambda expression as the second argument for the Func<int, string> parameter that the Paginator method takes. The lambda expression takes a page parameter, passes it to Url.Action() which returns a string (the resulting URL). The page parameter type is strongly typed to be an int as this is inferred from the implicit conversion of the lambda expression to the strongly typed Func<int, string> delegate. So...
the incremented int i from the loop inside the Paginator method is passed to the Func<int,string> method passed into the Paginator method, which gets bound to the page parameter of the lambda expression and returns the URL. Does that explain it?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.