I have a Href that takes me to a the next page i would like it to return a view with the PageNumber + 1 however every time i click it the value does not increase.
Here is the code
public async Task<IActionResultIndex(int? PageNumber = 1)
{
if (Category != null)
{
PageNumber = PageNumber + 1;
return View("page",new{PageNumber});
}
}
Modal
public class CategoryViewModel
{
public int PageNumber { get; set; }
}
HTML
Next page
Related
I have a PaginatedList<T> class inheriting from List<T> in an ASP.NET Core app that my controllers and views use to render paginated content:
public class PaginatedList<T> : List<T>
{
public int PageNumber { get; private set; } = 1; // page index
public int PageSize { get; private set; } = 25; // item count in each page
public int TotalCount { get; private set; } // total items in all pages
public int PageCount { get; private set; } // total page count
public bool HasPreviousPage => PageNumber > 1;
public bool HasNextPage => PageNumber < PageCount;
public int From => PageSize * (PageNumber - 1) + 1; // index of first item in page
public int To => From + Count - 1; // index of last item in page
//Method bodies omitted for brevity
public static async Task<PaginatedList<T>> CreateAsync(
IQueryable<T> source, int pageSize = 25, int pageNumber = 1) {..}
private PaginatedList(
List<T> items, int count, int pageSize = 25, int pageNumber = 1) {..}
}
Code based on this with additional validation.
I have noticed that almost all of my views serving paginated lists have the same page navigation UI. (For all entities in the app like Person, Book, etc.):
#model PaginatedList<Person>
#*View-specific code here...*#
<nav aria-label="Page Navigation">
#if (Model.Count > 1)
{
#* Display the range of entities displayed *#
<div class="text-center text-muted my-2">
<em>Showing #Model.From to #Model.To out of #Model.TotalCount</em>
</div>
}
<ul class="pagination justify-content-center">
#if (Model.HasPreviousPage || Model.HasNextPage)
{
#if (Model.HasPreviousPage)
{
<li class="page-item">
<a asp-controller="Person"
asp-action="Index"
asp-route-repoId="#Model.RepoId"
asp-route-page="#(Model.PageNumber - 1)"
title="Previous" class="page-link" aria-label="Previous">
<span class="sr-only">Previous</span>«
</a>
</li>
}
else
{
<li class="page-item disabled">
<a class="page-link" tabindex="-1">
<span class="sr-only">Previous</span>«
</a>
</li>
}
<li class="page-item active" aria-current="page">
<a class="page-link" href="#">#Model.PageNumber</a>
</li>
#if (Model.HasNextPage)
{
<li class="page-item">
<a
asp-controller="Person"
asp-action="Index"
asp-route-repoId="#Model.RepoId"
asp-route-page="#(Model.PageNumber + 1)" title="Next" class="page-link" aria-label="Next">
<span class="sr-only">Next</span>»
</a>
</li>
}
else
{
<li class="page-item disabled">
<a class="page-link" tabindex="-1">
<span class="sr-only">Next</span>»
</a>
</li>
}
}
</ul>
</nav>
I would like to extract this to a partial view called _PageNav.cshtml. But there are two things I'm not quite sure about:
The PaginatedList<T> class is generic and I cannot have a partial view with a model called PaginatedList<T> with an open generic parameter T. One possible solution I can think of is writing a non-generic interface called IPaginatedList that has all the properties required for page navigation and then have PaginatedList<T> implement it. This is because the navigation UI does not need to know anything about the items in the list. Then I can use IPaginatedList as the model for my partial view:
public interface IPaginatedList
{
public int PageNumber { get; }
public int PageSize { get; }
public int TotalCount { get; }
public int PageCount { get; }
public bool HasPreviousPage { get; }
public bool HasNextPage { get; }
public int From { get; }
public int To { get; }
}
public class PaginatedList<T> : List<T>, IPaginatedList
{
...
}
_PageNav.cshtml:
#model IPaginatedList
#* Navigation UI *#
...
This would kind of solve the first issue.
The second issue is that each view with a paginated list has a different previous and next page link. (the area/controller/action name for the link and different route values):
#* Previous page link *#
<a asp-controller="Persons"
asp-action="Index"
asp-route-repoId="#Model.RepoId"
asp-route-page="#(Model.PageNumber - 1)"
title="Previous"
class="page-link"
aria-label="Previous">
<span class="sr-only">Previous</span>«
</a>
#* Next page link *#
<a asp-controller="Persons"
asp-action="Index"
asp-route-repoId="#Model.RepoId"
asp-route-page="#(Model.PageNumber + 1)"
title="Next"
class="page-link"
aria-label="Next">
<span class="sr-only">Next</span>»
</a>
The above would be the previous and next links for the Index action of PersonsController (Person entity). This would change for another entity like Book.
Should I have a property for each link tag helper argument in my PaginatedList<T> like ControllerName, ActionName, etc.? Is there a better way?
Can this be solved with a custom tag-helper? What am I missing?
I have solution for using tag helpers
public class PaginationTagHelper : TagHelper
{
[HtmlAttributeName("onclick-method-name")]
public string OnClick { get; set; }
[HtmlAttributeName("current-page-index")]
public int CurrentPage { get; set; }
[HtmlAttributeName("end-page")]
public int EndPage { get; set; }
[HtmlAttributeName("start-page")]
public int StartPage { get; set; }
[HtmlAttributeName("total-page-count")]
public int TotalPages { get; set; }
/// <summary>
/// Process
/// </summary>
/// <param name="context"></param>
/// <param name="output"></param>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var onclickMethodFormat = OnClick + "({0})"; // 0 - pagenumber .
var onclickMethod = string.Empty;
StringBuilder content = new StringBuilder();
content.Append("<nav aria-label=\"Page navigation example\" class=\"pagin-holder clearfix\"> <ul class=\"pagination pagination-sm\">");
if (EndPage > 1)
{
if (CurrentPage > 1)
{
onclickMethod = string.Format(onclickMethodFormat, CurrentPage - 1);
content.Append("<li class=\"page-item\"> Previous</li>");
}
for (var page = StartPage; page <= EndPage; page++)
{
onclickMethod = string.Format(onclickMethodFormat, page);
if (page == CurrentPage)
{
content.Append("<li class=\"page-item active\">").Append(page).Append(" ");
}
else
{
content.Append("<li class=\"page-item\">").Append(page).Append(" ");
}
}
if (CurrentPage < TotalPages)
{
onclickMethod = string.Format(onclickMethodFormat, CurrentPage + 1);
content.Append("<li class=\"page-item\"> Next </li>");
}
}
output.Content.AppendHtml(content.ToString());
}
}
This is my custom pagination class
public class PagingInfo
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public string SortOrder { get; set; }
public byte SortColumn { get; set; }
public string SortColumnName { get; set; }
public int TotalRecords { get; set; }
public string SearchText { get; set; }
public byte FilterByColumn { get; set; }
public string FilterByColumnName { get; set; }
}
public class Pager
{
public Pager(long totalItems, int? page, int pageSize = 10)
{
// calculate total, start and end pages
var totalPages = (int)Math.Ceiling((decimal)totalItems / (decimal)pageSize);
var currentPage = page != null ? (int)page : 1;
var startPage = currentPage - 5;
var endPage = currentPage + 4;
if (startPage <= 0)
{
endPage -= (startPage - 1);
startPage = 1;
}
if (endPage > totalPages)
{
endPage = totalPages;
if (endPage > 10)
{
startPage = endPage - 9;
}
}
TotalItems = totalItems;
CurrentPage = currentPage;
PageSize = pageSize;
TotalPages = totalPages;
StartPage = startPage;
EndPage = endPage;
}
public long TotalItems { get; set; }
public int CurrentPage { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public int StartPage { get; set; }
public int EndPage { get; set; }
}
Added tag helper in view:
<Pagination onclick-method-name="Index" current-page-index="Model.PagingInfo.CurrentPage" end-page="Model.PagingInfo.EndPage"
start-page="Model.PagingInfo.StartPage" total-page-count="Model.PagingInfo.TotalPages"></Pagination>
I have the following paging logic that works very well. If I decide to load 5 items at the time (each time) it works fine. However I have the following scenario: I want to initially load 10 items and then each time user clicks load more I want to load 5 items. How can I do that?
public class Page
{
public Page(int totalItems, int? page, int pageSize = 5)
{
var totalPages = (int)Math.Ceiling((decimal)totalItems / (decimal)pageSize);
var currentPage = page != null ? (int)page : 1;
var startPage = currentPage - 5;
var endPage = currentPage + 4;
if (startPage <= 0)
{
endPage -= (startPage - 1);
startPage = 1;
}
if (endPage > totalPages)
{
endPage = totalPages;
if (endPage > 10)
{
startPage = endPage - 9;
}
}
TotalItems = totalItems;
CurrentPage = currentPage;
PageSize = pageSize;
TotalPages = totalPages;
StartPage = startPage;
EndPage = endPage;
StartIndex = (CurrentPage - 1) * PageSize;
EndIndex = Math.Min(StartIndex + PageSize - 1, TotalItems - 1);
}
public int TotalItems { get; private set; }
public int CurrentPage { get; private set; }
public int PageSize { get; private set; }
public int TotalPages { get; private set; }
public int StartPage { get; private set; }
public int EndPage { get; private set; }
public int StartIndex { get; set; }
public int EndIndex { get; set; }
}
You can check if page is zero or 1 depending on the index you started if so this means that it is the first time and return 10 not five, then continue returning 5 as usual since page will be increased and the condition will not apply anymore
I'm new to C# and I try to find a way to pass a generic delegate to a constructor.
When calling a show endpoint, if there is pagination, the endpoint should return a Page instance. When creating the Page instance, the delegate method should be passed as one of constructor parameters. ShowsEndpoints will host many endpoints and many of these endpoints use pagination. I want to use only one class to handle all endpoints.
public partial class ShowsEndpoints : Endpoint
{
public Func<Task<IEnumerable<object>>> FetchPopularShowsAsyncDelegate;
public Func<Task<IEnumerable<object>>> FetchTrendingShowsAsyncDelegate;
public Func<Task<IEnumerable<object>>> FetchMostWatchedShowsAsyncDelegate;
public async Task<IEnumerable<object>> FetchPopularShowsAsync(int page = 1, int limit = 20)
{
return await SendAsync(new PopularShowsRequest(page, limit));
}
public async Task<IEnumerable<object>> FetchTrendingShowsAsync(int page = 1, int limit = 20)
{
return await SendAsync(new TrendingShowsRequest(page, limit));
}
public async Task<IEnumerable<object>> FetchMostWatchedShowsAsync(int page = 1, int limit = 20)
{
return await SendAsync(new MostWatchedShowsRequest(page, limit));
}
}
Page class which will call the delegate to fetch the next page if there is a next page.
public class Page<TItem, TDelegate>
{
private TDelegate _delegateCommand;
public int CurrentPage { get; }
public int TotalPages { get; }
public int PageItemsCount { get; }
public int TotalItemsCount { get; }
public IEnumerable<TItem> Items { get; }
public Page(int page, int totalPages, int itemsCount, int totalItemsCount, IEnumerable<TItem> items, TDelegate delegateCommand)
{
CurrentPage = page;
TotalPages = totalPages;
PageItemsCount = itemsCount;
TotalItemsCount = totalItemsCount;
Items = items;
_delegateCommand = delegateCommand;
}
public bool HasNext()
{
return CurrentPage <= TotalPages;
}
public bool HasPrevious()
{
return CurrentPage >= 1;
}
public Page<TItem, TDelegate> Next()
{
// call _delegateCommand to fetch the next page items
}
public Page<TItem, TDelegate> Previous()
{
// call _delegateCommand to fetch the previous page items
}
}
Is it possible ?
Thank you.
EDIT
I have updated my classes code in order to understand better my question. I have 3 endpoints associated with their delegates and I want the Page class to use any of these 3 delegates with the same code to avoid duplication. So _delegateCommand can be FetchPopularShowsAsyncDelegate, FetchTrendingShowsAsyncDelegate or FetchMostWatchedShowsAsyncDelegate.
I'm not sure if I've got the purpose of _delegateCommand right - I guess it should be the function returning IEnumerable<TItem>. If so, then you can declare it precisely like that, and then you won't have to introduce another generic parameter:
public class Page<TItem>
{
private Func<IEnumerable<TItem>>_delegateCommand;
public int CurrentPage { get; }
public int TotalPages { get; }
public int PageItemsCount { get; }
public int TotalItemsCount { get; }
public IEnumerable<TItem> Items { get; }
public Page(int page, int totalPages, int itemsCount,
int totalItemsCount, IEnumerable<TItem> items,
Func<IEnumerable<TItem>> delegateCommand)
{
CurrentPage = page;
TotalPages = totalPages;
PageItemsCount = itemsCount;
TotalItemsCount = totalItemsCount;
Items = items;
_delegateCommand = delegateCommand;
}
public bool HasNext()
{
return CurrentPage <= TotalPages;
}
public bool HasPrevious()
{
return CurrentPage >= 1;
}
}
My controller class contains
var Paged = new PaginatedList<Products>(SideBar, page ?? 0, pageSize);
if (Request.IsAjaxRequest())
{
return PartialView("~/Views/Shared/_Grid.cshtml", Paged);
}
return View(Paged);
the PaginatedList is
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int PageSize { get; private set; }
public int TotalCount { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
PageSize = pageSize;
TotalCount = source.Count();
TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);
this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
}
public bool HasPreviousPage
{
get
{
return (PageIndex > 0);
}
}
public bool HasNextPage
{
get
{
return (PageIndex + 1 < TotalPages);
}
}
}
And my view is
<div class="pagination-container">
<nav class="pagination">
<ul>
#for (int i = 0; i < Model.TotalPages; i++)
{
<li><a href="#Url.Action("Index", "Home", new { page = i})"
class="#(i == Model.PageIndex ? "current-page" : "")">#(i + 1)</a></li>
}
</ul>
</nav>
<nav class="pagination-next-prev">
<ul>
#if (Model.HasPreviousPage) {
<li></li>
}
#if (Model.HasNextPage) {
<li></li>
}
</ul>
</nav>
<div>
Page #(Model.PageIndex + 1) of #Model.TotalPages
</div>
</div>
One problem with the view above, is that it creates numeric pages equal to the page sizes within model. If the model has 6 pages the result is
What will happen if i have 100 Model.Pages ?
You need to make sure that you separate you're not returning all the data in the model - you return only return the page size, at most, and have a separate property to store the total count.
For instance:
public class PageResult<T> where T : class
{
public PageResult(IEnumerable<T> items, int page = 1, int pageSize = 10)
{
this.Items = items;
this.PageSize = pageSize;
this.PageIndex = page;
this.PaginateData(page);
}
public IEnumerable<T> Items { get; private set; }
public int PageSize { get; private set; }
public int PageIndex { get; private set; }
public int Total { get; private set; }
public int TotalPages
{
get
{
return Math.Max((int)Math.Ceiling((Total / (double)PageSize)), 1);
}
}
private int _MaxPagesToDisplay = 10;
public int MaxPagesToDisplay
{
get { return _MaxPagesToDisplay; }
set { _MaxPagesToDisplay = value; }
}
public int PagesToDisplay
{
get
{
return Math.Min(this.MaxPagesToDisplay, TotalPages);
}
}
public void PaginateData(int page)
{
if(this.Items == null) return;
this.Total = this.Items.Count();
this.Items = this.Items.Skip(this.PageSize * this.PageIndex - 1).Take(this.PageSize);
}
}
This will mean you can return for example 1 million results and set the Total to 1 million, but only insert 10 into the Items collection. You can use Linq to paginate the data into this collection. I added the method PaginateData which does it for you.
Then you can update your view:
#for (int i = 1; i <= Model.PagesToDisplay; i++)
{
<li><a href="#Url.Action("Index", "Home", new { page = i})"
class="#(i == Model.PageIndex ? "current-page" : "")">#(i)</a></li>
}
You can then use the Total field to display the total count on the page.
I am building a pagination my mvc project and have follew problem.
I did everything for pagination and now need just pass a page information to view data in view.
I have a user control Pagination:
Pagination.ascx:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="Pagination.ascx.cs"
Inherits="PIMP.Web.TestForum.Views.Shared.Pagination" %>
<ul id="pagination-flickr">
<% if (ViewData.Model.HasPreviousPage)
{ %>
<li class="previous"><a href="<%=ViewData.PageActionLink.Replace("%7Bpage%7D", (ViewData.PageIndex - 1).ToString())%>">
« Previous</a></li>
<% }
else
{ %>
<li class="previous-off">« Previous</li>
<% } %>
<%for (int page = 1; page <= ViewData.Model.TotalPages; page++)
{
if (page == ViewData.Model.PageIndex)
{ %>
<li class="active">
<%=page.ToString()%></li>
<% }
else
{ %>
<li><a href="<%=ViewData.PageActionLink.Replace("%7Bpage%7D", page.ToString())%>">
<%=page.ToString()%></a></li>
<% }
}
if (ViewData.Model.HasNextPage)
{ %>
<li class="next"><a href="<%=ViewData.PageActionLink.Replace("%7Bpage%7D", (ViewData.PageIndex + 1).ToString())%>">
Next »</a></li>
<% }
else
{ %>
<li class="next-off">Next »</li>
<% } %>
</ul>
<ul id="pagination-flickr0">
<li></li>
</ul>
Pagination.ascx.cs:
public partial class PaginationViewData
{
public int PageIndex { get; set; }
public int TotalPages { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public string PageActionLink { get; set; }
public bool HasPreviousPage
{
get
{
return (PageIndex > 1);
}
}
public bool HasNextPage
{
get
{
return (PageIndex * PageSize) <= TotalCount;
}
}
}
public partial class Pagination : System.Web.Mvc.ViewUserControl<PaginationViewData>
{
public Pagination()
{
}
}
class PagedList.cs:
namespace PIMP.Web.TestForum.DataObject.Forum
{
public class PagedList<T>: List<T>
{
public PagedList(IQueryable<T> source, int index, int pageSize)
{
this.TotalCount = source.Count();
this.PageSize = pageSize;
this.PageIndex = index;
this.AddRange(source.Skip((index - 1) * pageSize).Take(pageSize).ToList());
int pageResult = 0;
for (int counter = 1; pageResult < this.TotalCount; counter++)
{
pageResult = counter * this.PageSize;
this.TotalPages = counter;
}
}
public PagedList()
{
}
public int TotalPages
{
get;
set;
}
public int TotalCount
{
get;
set;
}
public int PageIndex
{
get;
set;
}
public int PageSize
{
get;
set;
}
public bool HasPreviousPage
{
get
{
return (PageIndex > 1);
}
}
public bool HasNextPage
{
get
{
return (PageIndex * PageSize) <= TotalCount;
}
}
}
public static class Pagination
{
public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, int index, int pageSize)
{
return new PagedList<T>(source, index, pageSize);
}
public static PagedList<T> ToPagedList<T>(this IQueryable<T> source, int index)
{
return new PagedList<T>(source, index, 10);
}
}
}
in View I am doing following:
<% Html.RenderPartial("Pagination", new NISE.Web.TestForum.Views.Shared.PaginationViewData()
{
PageIndex = ViewData.Model.PageIndex,
TotalPages = ViewData.Model.TotalPages,
PageActionLink = Url.Action("Forum", "Thread", new { id = this.Model.Id, page = "{page}" }),
TotalCount = ViewData.Model.TotalCount,
PageSize = ViewData.Model.PageSize
}, null);%>
I dont know what I should do in my controller.
How can I pass Page Information to the ViewData?
My controller looks like that, can you help me to extend it??
[HttpGet]
[ValidateInput(false)]
public ActionResult Thread(Guid id)
{
try
{
ThreadModel model = new ThreadModel(id);
model.IncreaseHitCount();
PagedList<ListView> list = new PagedList<ListView>();
list.PageIndex = 0;
list.PageSize = 0;
list.TotalCount = 0;
list.TotalPages = 0;
return View(model);
}
catch (Exception ex)
{
bool rethrow = ExceptionHelper.Handle(ex, "Business Logic");
if (rethrow)
{
throw;
}
}
return View();
}
Well, I did it like this:
I created a "view-model" of (my custom) data grid which contains infos about the current page, page size, etc.
it looks like this:
using System.Collections.Generic;
public class GridContent<T>
{
public IEnumerable<T> Data { get; set; }
public int PageIndex { get; set; }
public int TotalPages { get; set; }
public int TotalItems { get; set; }
}
in the controller I then return the following:
return View(new GridContent<Entity>()
{
Data = entityEnumeration, // your actual data
PageIndex = pageIndex,
TotalItems = totalItems,
TotalPages = totalItems / pageSize + 1
});
and finally on the view
<%# Page Title="SomeTitle" Language="C#" Inherits="System.Web.Mvc.ViewPage<GridContent<Entity>>" %>
Now you can access the pagination information as well as the data using the wrapper model / class GridContent.
Hope this helps.