Justin R. Buchanan

on Software Development, Systems Administration, Networking, and Random other Stuff

Return Paged Data from an IQueryable<T>

I finally broke down and wrote a standard extension method for IQueryable<T> that returns a single “page” of data from a source set. This method is terminal if it is in an expression tree in that it returns an IEnumerable<T> and not IQueryable<T>. This method works by calling Count() to get the total number of records (which it sends back in the output parameter itemCount), and then uses the normal .Skip() and .Take() method to return a single page. It should work against any IQueryable<T> including LINQ to SQL and Entity Framework sequences.

/// <summary>
/// Gets a single page of items from a sequence.
/// </summary>
/// <typeparam name="T">The data type of the result items.</typeparam>
/// <param name="query">The sequence</param>
/// <param name="pageNumber">The page number to retrieve, starting at 1.</param>
/// <param name="pageSize">The number of items in each page.</param>
/// <param name="pageCount">Provides the total number of pages available.</param>
/// <returns></returns>
public static IEnumerable<T> TakePage<T>(this IQueryable<T> query, int pageNumber, int pageSize, out int pageCount)
{
    int itemCount;
    return TakePage(query, pageNumber, pageSize, out pageCount, out itemCount);
}

/// <summary>
/// Gets a single page of items from a sequence.
/// </summary>
/// <typeparam name="T">The data type of the result items.</typeparam>
/// <param name="query">The sequence</param>
/// <param name="pageNumber">The page number to retrieve, starting at 1.</param>
/// <param name="pageSize">The number of items in each page.</param>
/// <param name="pageCount">Provides the total number of pages available.</param>
/// <param name="itemCount">Provides the total number of items availabe.</param>
/// <returns></returns>
public static IEnumerable<T> TakePage<T>(this IQueryable<T> query, int pageNumber, int pageSize, out int pageCount, out int itemCount)
{
    if (pageNumber < 1)
        throw new ArgumentException("The value for 'page' must be greater than or equal to 1", "pageNumber");

    itemCount = query.Count();

    pageCount = (int)Math.Ceiling((double)itemCount / (double)pageSize);

    if (pageNumber > pageCount)
        pageNumber = pageCount;

    if (pageNumber > 1)
        return query.Skip((pageNumber - 1) * pageSize).Take(pageSize);
    else
        return query.Take(pageSize);
}

ASP.NET MVC HtmlHelper Extension Method for Menu Highlighting

I built an extension method on the ASP.NET MVC HtmlHelper class to handle the creation of navigation menu links (tabs) that have a different CSS class applied to them if they are active (i.e. the current page). I had been doing this manually in the master layout (_Layout.cshtml) view with a bunch of if blocks. Of course you can roll out a full-fledged sitemap to handle this, but I didn’t want to.

/// <summary>
/// Extension method for <see cref="HtmlHelper"/> to support highlighting the active tab on the default MVC menu
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="linkText">The text to display in the link</param>
/// <param name="actionName">Link target action name</param>
/// <param name="controllerName">Link target controller name</param>
/// <param name="activeClass">The CSS class to apply to the link if active</param>
/// <param name="checkAction">If true, checks the current action name to determine if the menu item is 'active', otherwise only the controller name is matched</param>
/// <returns></returns>
public static MvcHtmlString MenuLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, string activeClass, bool checkAction)
{
    string currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
    string currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");

    if (string.Compare(controllerName, currentController, StringComparison.OrdinalIgnoreCase) == 0 && ((!checkAction) || string.Compare(actionName, currentAction, StringComparison.OrdinalIgnoreCase) == 0))
    {
        return htmlHelper.ActionLink(linkText, actionName, controllerName, null, new { @class = activeClass });
    }

    return htmlHelper.ActionLink(linkText, actionName, controllerName);
    
}

activeClass sets the CSS class name that will be applied, and setting checkAction to true applies the class only if the action name and controller name match.

So my in my _Layout.cshtml view, I changed all the Html.ActionLink calls to Html.MenuLink and added the active and checkAction parameters.

<nav>
    <ul id="menu">                        
        <li>@Html.MenuLink("Home", "Index", "Home", "active", true)</li>
        <li>@Html.MenuLink("About", "About", "Home", "active", true)</li>
        <li>@Html.MenuLink("Contact", "Contact", "Home", "active", true)</li>
        <li>@Html.MenuLink("Something Else", "Index", "Something", "active", false)</li>
    </ul>
</nav>   

The Home, About and Contact actions are all handled by the HomeController, and since they are distinct menu choices, we set checkAction to true. The Something Else option, on the other hand, should be highlighted anytime we hit an action on the SomethingController.

One final tweak was to add an overload to default the checkAction parameter to true.

public static MvcHtmlString MenuLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, string activeClass)
{
    return MenuLink(htmlHelper, linkText, actionName, controllerName, activeClass, true);
}

And the updated view code:

<nav>
    <ul id="menu">                        
        <li>@Html.MenuLink("Home", "Index", "Home", "active")</li>
        <li>@Html.MenuLink("About", "About", "Home", "active")</li>
        <li>@Html.MenuLink("Contact", "Contact", "Home", "active")</li>
        <li>@Html.MenuLink("Something Else", "Index", "Something", "active", false)</li>
    </ul>
</nav>