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
Hi there,
I am currently testing the paging extended modification suggested in your web post. However, I encountered a problem whereby the pagesize value were not set, ie always to default. Specifically, in the view:
@Html.PagedListPager(Model, page => Url.Action(ActionNames.Index,
new { page, itemsPerPage = ViewBag.CurrentItemsPerPage }));
the itemsPerpage returned to the controller is always null no matter what value is selected in the dropdownlist.
If I were to set the ViewBag.CurrentItemsPerPage in the controller to a value, then this hardcoded value persists throughout; but this is not what we want.
Your help is very much appreciated.
Thanks.
Liu,
I did some testing and found one scenario where the selected number of items (
itemsPerPage
) is not passed to the controller.In my tests this happened when the
DropDownList
used to select the number of items is not located inside the scope of@using (Html.BeginForm(...)
in the view.Hth,
Stefan
Hi Stefan,
Thx.
Best Rgds,
Liu
You’re welcome 🙂
Stefan
Thanks my friend! What I did is the following:
// in Controller
public ViewResult Index(int? page = null, int? pageSize = null)
{
ViewBag.CurrentItemsPerPage = pageSize ?? 10; // default 10
// … do all to get list
return View(list.ToPagedList(page ?? 1, pageSize ?? 10);
}
// in View. As is Index action I didn´t have a beginForm I added it just for pageSize
@using (Html.BeginForm(“Index”, “ControllerName”, FormMethod.Post))
{
@Html.DropDownList(“pageSize”, DefaultValues.ItemsPerPageList, new { @id = “pageSize”, onchange=”this.form.submit();” })
}
// At the bottom when PagedList paginates I added the pageSize
@Html.PagedListPager(Model, page => Url.Action(“Index”, new { page, pageSize = ViewBag.CurrentItemsPerPage }))
The paging will be done with default 10 records per page. When you change the items per page in the dropdown list, a submit will be done to show the selected items per page value. Then the paging will be performed remembering the chosen value.
Hope it helps! 😀
Would someone be willing to link to the view code they have that’s working?
I’m trying to figure out where to put the DefaultValues class.
I’ve tried putting the in C# (next to the controller code), putting it in the view in various places… (above, below, inside “@using (Html.BeginForm(…”, outside it… no matter where I put it, I get the error (when I run the page):
The name ‘DefaultValues’ does not exist in the current context
from this line:
@Html.DropDownList(“ItemsPerPage”, DefaultValues.ItemsPerPageList, new { @id = “ItemsPerPageList”, onchange=”this.form.submit();” })
So… where does it go!?
[in the view, when it wasn’t seeming to be treated as static text (i.e. it had syntax highlighting), then the compiler said “List” wasn’t a valid value — it was expecting something like “List” or something like that… but even when I added that, it still doesn’t work.
Put the DefaultValues class into its own .cs file. Do not try to put it into .cshtml files.
Hth,
Stefan
Excellent article.
Can we have a complete code with all the changes you have specified above that would be really helpful.
Thanks,
Fenil,
I’m sorry but I haven’t put the changes together. In case you are going to do so and publish it in your blog, please let me know. I will add a link to it then.
Thanks,
Stefan
Hello ! Thanks for this! But i have a problem :
this is my controller
…..
// Pour pagination
SelectList itemsPerPageList = DefaultValues.ItemsPerPageList;
itemsPerPage = (int)itemsPerPageList.SelectedValue;
ViewBag.CurrentItemsPerPage = itemsPerPage;
int pageNumber = (page ?? 1);
…..
return View(“ResultatView”, ListeResultatViewModel.ToPagedList(pageNumber, itemsPerPage ?? 10));
This my DefaultValues class :
public static class DefaultValues
{
public static SelectList ItemsPerPageList
{
get
{
return (new SelectList(new [] {10, 25, 50, 100 },
selectedValue: 10));
}
}
}
And finally my view :
in My BeginForm(..)
if (Model != null)
{
@Html.DropDownList(“ItemsPerPage”,DefaultValues.ItemsPerPageList, new { @id = “ItemsPerPageList” })
}
and out :
@Html.PagedListPager(Model, page => Url.Action(“ResultatView”, new { page, sortOrder = ViewBag.CurrentSort, itemsPerPage = ViewBag.CurrentItemsPerPage }))
@if (Model != null && Model.PageCount > 0)
{
Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) sur @Model.PageCount (@(Model.TotalItemCount))
}
When i change the value in my dropdown, nothing happens
have some ideas ?
Thanks
Where does the –
public static class DefaultValues
{
…
public static SelectList ItemsPerPageList
{
get
{ return (new SelectList(new List { 5, 10, 25, 50, 100 },
selectedValue: 10));
}
}
…
}
go?
This is a new class which should go into its own file. It is used by the view.
Hope this answers your question. If not, please give more details.
Regards,
Stefan
On Paging i m getting only 1 when their is 376 pages why
@Html.PagedListPager(Model, page => Url.Action(“Index”,new { page }))
Oh, the deferred execution of Entity Framework ensures the paging is done server side. In fact a call to the database is only made when I do