Картинка блога

Я за свою сознательную жизнь написал с десяток  классов для организации постраничего вывода на разных языках и под разные технологии. С приходом LINQ, в C# теперь можно сделать его универсальным. Я не претендую на абсолютную правильность изложенного, тем не менее, я гарантирую, что предложенные методы работают.

WCF и Paging

Допустим у нас есть страница, к базе данных которая подключена через WCF. IQueryable не сериализуется (ну и не надо, зачем нам писать логику построения запроса на уровне презентации), это значит нам надо передавать информацию о текущей странице через параметры. Значит нам нужен следующий класс:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace MyService.Data
{
///

/// Client and server paging information holder.
/// Used in PagingListPair by composition.
/// Or created directly from client side.
///

[DataContract]
[Serializable]
public class PagingInformation
{
///

/// Client paging creation helper method
///

/// page index to show /// page size to show (amount of entries per page index) ///
static public PagingInformation CreateClientPaging(int index, int size)
{
var pageInfo = new PagingInformation();
pageInfo.PageIndex = index;
pageInfo.RecordsPerPage = size;
return pageInfo;
}
///

/// Total amount of the entries
///

[DataMember]
public long? Count { get; set; }

///

/// Amount of records to show
///

[DataMember]
public int? RecordsPerPage { get; set; }

///

/// Current page index
///

[DataMember]
public int? PageIndex { get; set; }
///

/// Couns the page amount from and infromation.
///

public int PageCount
{
get
{
if (!Count.HasValue || !RecordsPerPage.HasValue)
{
throw new InvalidOperationException("Invalid initialization of count or recordsAmount value");
}

decimal numOfPages = (decimal)Count.Value / (decimal)RecordsPerPage.Value;
numOfPages = Math.Ceiling(numOfPages);
return (int)numOfPages;

//return (int)Math.Round((decimal)(tmpCount / tmpRecPerPage), MidpointRounding.AwayFromZero);
//return (int)Math.Ceiling((decimal)(Count.Value / RecordsPerPage.Value));
}
}


}
}

Теперь на стороне клиента можно использовать CreateClientPaging, для того чтобы получить информацию о страницах и сами данные нам нужен композитный класс, который будет передаватся в качестве ответа:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace MyService.Data
{
///

/// Paging helper class.
/// Holds paging information performed to query.
/// Used by
///

///
[DataContract]
[Serializable]
public class PagingListPair
{
///

/// PAging information of how the lentity list was generated.
///

[DataMember]
public PagingInformation PagingInformation { get; set; }

[DataMember]
public List EntityList { get; set; }
}
}

Вот и все, добавляем к нашему контракту аттрибуты:

[ServiceKnownType(typeof(PagingInformation))]
[ServiceKnownType(typeof(MyEntity))]
[ServiceKnownType(typeof(PagingListPair))]

А в методах сервиса используем методы следующего формата:

PagingListPair GetMyEntities(PagingInformation pageInfo);

Для LINQ пейджинга можно использовать метод помошник:

public static PagingListPair ApplyPagination(IEnumerable entities, PagingInformation pageInfo)
{
if (!pageInfo.PageIndex.HasValue || !pageInfo.RecordsPerPage.HasValue || !(pageInfo.RecordsPerPage.Value > 0))
{
throw new InvalidOperationException("Invalid pageInforation values");
}
pageInfo.Count = entities.Count();
var output = new PagingListPair();
output.PagingInformation = pageInfo;
output.EntityList = entities.Skip(pageInfo.PageIndex.Value * pageInfo.RecordsPerPage.Value).Take(pageInfo.RecordsPerPage.Value).ToList();
return output;
}

 

ASP.NET стандартный пейджинг

В ASP.NET есть интерфейс IPageableItemContainer, он используется в DataBinging
Его реализация выглядит примерно так:


int _startIndex, _maxRows;
public int MaximumRows
{
get { return _maxRows; }
}
public int StartRowIndex
{
get { return _startIndex; }
}

public event EventHandler

TotalRowCountAvailable;

public void SetPageProperties(int startRowIndex, int maximumRows, bool databind)
{
_startIndex = startRowIndex;
_maxRows = maximumRows;
}

Onload***
{
if (TotalRowCountAvailable != null)
{
TotalRowCountAvailable(this, new PageEventArgs(_startIndex, _maxRows, _dataSource.Count));
_dataSource = _dataSource.Skip(_startIndex).Take(_maxRows).ToList();
}
}

Еще один метод постраничного вывода в качестве ASP.NET контрола.

Это PagingControl который можно использовать на страницах с простой логикой, класс можно переопределить для локализации и переопределения стилей, изменение текущей страницы можно получить после срабатывания события:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyControls
{
public enum SpecialPagingButtons
{
Prev, Next, First, Last
}
public class ResultListPager : Control, INamingContainer
{
public static int PageCountFromItems(int itemCount, int itemsPerPage) {
return (int)Math.Ceiling((0.0+itemCount)/itemsPerPage);
}

public event EventHandler PageIndexChanged;
private void OnPageIndexChanged()
{
if (PageIndexChanged != null)
{
PageIndexChanged(this, EventArgs.Empty);
}
}

public int CurrentPageIndex
{
get
{
if (ViewState["CurrentPageIndex"] == null)
ViewState["CurrentPageIndex"] = 0;
return (int)ViewState["CurrentPageIndex"];
}
set
{
if (CurrentPageIndex != value)
{
if ((value < 0) && (PageCount > 1))
{
throw new Exception("CurrentPageIndex must be greater than -1 if PagesCount is greater than 0");
}
if ((value > (PageCount - 1)) || (value < 0)) { throw new Exception("CurrentPageIndex is out of range"); } ViewState["CurrentPageIndex"] = value; } } } public int PageCount { get { if (ViewState["PageCount"] == null) ViewState["PageCount"] = 0; return (int)ViewState["PageCount"]; } set { if (PageCount != value) { if (value < 0) { throw new Exception("PageCount can not be smaller than zero."); } ViewState["PageCount"] = value; } } } int pageButtonCount = 3; public int PageButtonCount { get { return pageButtonCount; } set { if (value < 3) { throw new Exception("PageButtonCount can not be smaller than 3"); } pageButtonCount = value; } } bool showPrevNext = true; public bool ShowPrevNext { get { return showPrevNext; } set { showPrevNext = value; } } bool showFirstLast = true; public bool ShowFirstLast { get { return showFirstLast; } set { showFirstLast = value; } } protected virtual string GetItemStyle(int index) { return (index == CurrentPageIndex)?"item-selected":"item"; } protected virtual LinkButton CreateItem(string id, int idd) { return new LinkButton() { Text = (idd+1).ToString(), CssClass = GetItemStyle(idd) }; } protected virtual LinkButton CreateSpacialItem(string id, SpecialPagingButtons btn) { var ret = new LinkButton(); switch (btn) { case SpecialPagingButtons.Prev: ret.Text = "<"; ret.CssClass = GetItemStyle(CurrentPageIndex - 1); break; case SpecialPagingButtons.Next: ret.Text = ">";
ret.CssClass = GetItemStyle(CurrentPageIndex + 1);
break;
case SpecialPagingButtons.First:
ret.Text = "«";
ret.CssClass = GetItemStyle(0);
break;
case SpecialPagingButtons.Last:
ret.Text = "»";
ret.CssClass = GetItemStyle(PageCount-1);
break;
default:
break;
}
return ret;
}

private void AddSpecialItem(SpecialPagingButtons type) {
var id = "pg_" + type.ToString();
var linkButton = CreateSpacialItem(id, type);
linkButton.ID = id;
linkButton.Click += new EventHandler(specLinkButton_Click);
this.Controls.Add(linkButton);
}

protected override void CreateChildControls()
{
this.Controls.Clear();
this.ClearChildViewState();

var range = CalculateRange();
if (range.Length <= 1) return; if (CurrentPageIndex > 0 && ShowFirstLast) AddSpecialItem(SpecialPagingButtons.First);
if (CurrentPageIndex > 0 && ShowPrevNext) AddSpecialItem(SpecialPagingButtons.Prev);

foreach (int i in range)
{
var id = "pg_" + i.ToString();
var linkButton = CreateItem(id, i);
linkButton.ID = id;
linkButton.Click += new EventHandler(linkButton_Click);
this.Controls.Add(linkButton);
}

if (CurrentPageIndex 0)
{
writer.Write("

");
base.Render(writer);
writer.Write("

");
}
}

void specLinkButton_Click(object sender, EventArgs e)
{
var btn = (SpecialPagingButtons)Enum.Parse(typeof(SpecialPagingButtons), (sender as LinkButton).ID.Substring(3));

switch (btn)
{
case SpecialPagingButtons.Prev:
if (CurrentPageIndex > 0) CurrentPageIndex--;
break;
case SpecialPagingButtons.Next:
if (CurrentPageIndex < PageCount-1) CurrentPageIndex++; break; case SpecialPagingButtons.First: CurrentPageIndex = 0; break; case SpecialPagingButtons.Last: CurrentPageIndex = PageCount - 1; break; default: break; } CreateChildControls(); OnPageIndexChanged(); } void linkButton_Click(object sender, EventArgs e) { CurrentPageIndex = Int32.Parse((sender as LinkButton).ID.Substring(3)); CreateChildControls(); OnPageIndexChanged(); } public int[] CalculateRange() { int end, start; if (PageCount <= PageButtonCount) { start = 0; end = PageCount - 1; } else { int pagesLeft = (PageButtonCount - 1) / 2; int pagesRight = (PageButtonCount - pagesLeft); if (CurrentPageIndex + pagesRight > (PageCount - 1))
{
pagesRight = (PageCount - 1) - CurrentPageIndex;
pagesLeft = (PageButtonCount - pagesRight) - 1;
}
else
{
if (CurrentPageIndex - pagesLeft < 0) { pagesLeft = CurrentPageIndex; pagesRight = (PageButtonCount - pagesLeft) - 1; } } if (pagesLeft + pagesRight == PageButtonCount) pagesRight--; start = CurrentPageIndex - pagesLeft; end = CurrentPageIndex + pagesRight; } int[] result = new int[(end - start) + 1]; int a = 0; for (int i = start; i < end + 1; i++) { result[a] = i; a++; } return result; } } }

Paging в ASP.NET MVC

Когда я познакомился с MVC, больше мне не хотелось изобретать велосипед, я нашел библиотеку PagedList.

Реализация позожа на мой WCF Paging, пример кода можно посмотреть на основной странице проекта.

Метки:, ,

3 комментария в “ASP.NET пейджинг. (Paging in ASP.NET)”

  1. Как толково написано, я ввоссторге от статьи.

  2. Полезная статья, думаю что обязательно пригодится.

  3. а нормально оформить было явно лень..