Paging Made Easy with ASP.NET MVC 5, Entity Framework 6 and PageList.Mvc

Preface

In former days, implementing paging sometimes took a little bit of time. That was independent from the used platform, native Windows Client or Web or whatever.

Fortunately, that was then and this is now. Implementing paging in an ASP.NET MVC 5 Entity Framework 6 application really became simple. Just a few lines of code, and you’re done.

The Basics

The basics of paging in ASP.NET MVC are described by the Getting Started with EF 6 using MVC 5 tutorial Sorting, Filtering, and Paging with the Entity Framework in an ASP.NET MVC Application.

As the title says, you need EF 6 and MVC 5. The author Tom Dykstra suggests to use PagedList.Mvc. NuGet Must Haves lists this package on top of the Top 20 packages for paging. So I thought I’ll give it a try.

Using PagedList.Mvc

The usage is simple. As described by the ASP.NET tutorial, the package needs to be installed. I used the menu: Tools / NuGet Package Manager / Manage NuGet Packages for Solution….

Changing the View

The model of the view that should contain paging needs to be changed from IEnumetablle<MyModel> to PagedList.IPagedList<MyModel>. Also, the using of PagedList.Mvc and a link to the PagedList stylesheet needs to be added. After changing the code, the first tree lines of the view look like this:

@model PagedList.IPagedList<MyModel>
@using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" 
  type="text/css" />

In case you want to show the current page and the total number of pages, add something like this to the appropriate place.

Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber)
 of @Model.PageCount

The last change is adding the paging buttons into the view.

@Html.PagedListPager(Model, 
  page => Url.Action("Index", new { page }) )

Changing the Controller

Because the view expects a different kind of model, the controller has to generate it. And because we need to return the content of different pages, the requested page is passed as a parameter. Given that db.MyModels returns something of type MyModel, the controller might look like this:

public ActionResult Index
  (
    int? page
  )
{
  var items = for item in db.MyModels select item;

  return (View(items.ToPagedList(pageNumber: page ?? 1, 
    pageSize: 10)));
}

That's really simple, isn't it.

Paging Extended

The code above works really good. But I want to extend the paging a little bit.

Let the User Choose

The number of items shown per page is fix in the sample above. I do not like that. So I added a dropdown list to the view to let the user choose how many items per page will be displayed.

The list of items shown by the dropdown list is generated by a helper method to make it reuseable.

public static class DefaultValues
{
  ...
  public static SelectList ItemsPerPageList 
    { 
      get 
      { return (new SelectList(new List { 5, 10, 25, 50, 100 }, 
        selectedValue: 10)); 
      } 
    }
  ...
}

To use this list, the view defines a local variable.

SelectList itemsPerPageList = DefaultValues.ItemsPerPageList;

And the dropdown list is placed wherever I like using the HtmlHelper

@Html.DropDownList("ItemsPerPage", itemsPerPageList, 
  new { @id = "ItemsPerPageList" })

To have the correct number of items per page shown when the user switches the page, we need to pass the current number of items to the controller. Therefor the HtmlHelper for the paging buttons needs to be extended.

@Html.PagedListPager(Model, page => Url.Action(ActionNames.Index,
  new { page, itemsPerPage = ViewBag.CurrentItemsPerPage }));

As you can see by this code, the controller needs some changes too.

public ActionResult Index
  (
    int? itemsPerPage,
    int? page
  )
{
  ViewBag.CurrentItemsPerPage = itemsPerPage;

  var items = for item in db.MyModels select item;

  return (View(items.ToPagedList(pageNumber: page ?? 1, 
    pageSize: itemsPerPage ?? 10)));
}

Handling an Empty List

From what I noticed, the Model of the view is null in case the list itself does not contain any items.

The extension method PagedListPager is not able to handle that. And of course, when showing the current page and the total number of pages, accessing a null reference leads to an exception too.

To avoid these exceptions, some more code is required.

@if (Model != null
  && Model.PageCount > 0)
  {
    <div>
      Page @(Model.PageCount < Model.PageNumber 
             ? 0 : Model.PageNumber) 
      of @Model.PageCount
    </div>
  }
@if (Model != null)
  {
    @Html.DropDownList("ItemsPerPage", itemsPerPageList, 
      new { @id = "ItemsPerPageList" })
}

Minor Stylesheet Additions

In my ASP.NET MVC application, I have a footer defined in the _Layout.cshtml file. In some cases I saw that the paging buttons partly were overlapped by the footer. To avoid this, I added a bottom margin to the pagination container style and put this into the Content/Site.css file:

.pagination-container {
  margin-bottom:25px;
}

Conclusion

Even with these small additions, paging is made really easy. What I was not looking at is the performance of the database access. For web applications with small databases, this does not seem to be important from my point of view. In case you do have a large database and complex database requests where performance is an issue, I think it's worth to check out the impact of this implementation.

Links

ASP.NET Tutorial: Sorting, Filtering, and Paging with the Entity Framework in an ASP.NET MVC Application

PagedList.Mvc on NuGet

NuGet Must Haves Top 20 NuGet packages for paging

Handle Deactivated JavaScript in ASP.NET MVC Web Application

Preface

Depending on what your ASP.NET MVC Web application is supposed to do, it might be necessary that the client’s browser has JavaScript support enabled.

If this is not the case, maybe you want to make sure the user will be informed and cannot use the application.

And because there is no JavaScript available, this has to be done in a non-script way.

Use The noscript-Tag & Redirect

To handle all this without any script is quite simple. HTML offers the noscript tag.

Inside the noscript area, one can add whatever HTML code is required.

Since I want the user to be redirected to another page, I will use meta http-equiv="refresh" content="0;…" to redirect the user to the page explaining that JavaScript is required to use my application.

Add It To The Layout Page

To make sure the user will be redirected independent which page should be opened, I add the noscsript area to the _Layout.cshtml page.

And because I do use a meta tag, the code is placed into the head area to avoid warnings that meta elements cannot be nested inside the body element.

In case you use different layout pages in your application, you should put the redirection code into a partial view and add this to all of your layout pages. If there is the need to change the redirection, there will be only one place that needs to be changed.

NoJScript Controller And View

I do have a ASP.NET MVC application, so want to redirect to an appropriate controller when JavaScript is disabled, not to a HTML page.

Accordingly, I add the NoJScript controller with an Index view to my project and redirect the user to /NoJScript. The view is used to inform the user that JavaScript support is required and can contain all non-script elements that are needed.

It is important that this view does not use the layout page itself. Otherwise there will be an endless redirection loop, because when the view is opened, the browser detects there is no JavaScript enabled, and again redirects to this view, and so forth.

Access Restrictions

In case you restrict access to your application by setting the AuthorizeAttribute either to your controller or by adding

filters.Add(new System.Web.Mvc.AuthorizeAttribute());

to the App_Start/FilterConfig.cs, remember to set the AllowAnonymousAttribute to the Index method of the NoJScript controller. Otherwise, the browser is hooked in an endless loop, switching between the login page and the JavaScript required page.

Putting It Together

Here are all changes listed.

In the _Layout.cshtml page, this section is added to the head element:

<head>
…
<noscript>
  <meta http-equiv="refresh" content="0;url=/NoJScript" />
</noscript>
…
<head>

The NoJScript controller is really simple:

public class NoJScriptController : Controller
{
  //
  // GET: /NoJScript/
  [AllowAnonymous]
  public ActionResult Index()
  {
    return View();
  }
}

And the view should contain a little bit more information than this sample:

@{
  ViewBag.Title = "JavaScript required";
  Layout = null; // <= Important!
}

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>@ViewBag.Title - My MVC App</title>

  @Styles.Render("~/Content/css")
  @Scripts.Render("~/bundles/modernizr")

</head>
<body>
 To use this application JavaScript is required.
</body>