‫Custom Paging بهینه و غیر Declarative در DataGrid

اگر بخواهید از الگوی MVP در برنامه‌های ASP.NET استفاده کنید بایستی بتوانید همه چیز را به CodeBehind انتقال دهید. این یعنی declarative و binding تقریباً تعطیل. یکی از امکانات مهمی که این وسط از دست می‌رود، امکان Paging بهینه کنترل GridView از طریق CodeBeind است. منظور از Paging بهینه فقط نمایش pager در قسمت پایین کنترل نیست، بلکه منظور این است که فقط رکوردهای مورد نیاز برای صفحه جاری (معمولاً ۱۰ تا ۲۰ تا) از دیتابیس فراخوانی شود. کنترل GridView از طریق DataSourceهای موجود در markup مثل ObjectDataSource یا SqlDataSource می‌تواند Paging بهینه را ارائه دهد. اما از طریق CodeBehind، یعنی handle کردن دستی event مربوط به Paging فقط یک Paging غیر بهینه ارائه می‌دهد. چون در CodeBehind راهی برای اعلام تعداد رکوردها به GridView وجود ندارد (+).

می‌شود به جای GridView از کنترل DataGrid استفاده کرد. DataGrid امکان Paging بهینه از داخل Code Behind را به خوبی پشتیبانی کرده و می‌توان آن را با استفاده از الگوی MVP به کار گرفت. برای این کار کافیست از eventهای SortCommand و PageIndexChanged و پراپرتی‌های VirtualItemCount و CurrentPageIndex استفاده نمایید. لطفاً نمونه زیر را ببینید.

‫مشکل با EnableSEOPaging

یکی از امکانات کنترل گرید تلریک یعنی RadGrid امکان SEO friendly بودن مکانیزم Paging آن می‌باشد. به این معنی که هر کدام از Pageهای گرید، URL مختص به خودشان را دارند. به عبارت دیگر با وارد کردن URL صفحه می‌توان به همان Page متناظر رسید. این امکان در کنترل استاندارد GridView وجود ندارد.

متاسفانه یکی از مشکلاتی که EnableSEOPaging ایجاد می‌کند پاک شدن دیگر کنترل‌های موجود در صفحه به هنگام رفت و آمد بین Pageهای مختلف گرید است. یعنی اگر از صفحه ۱ به صفحه ۲ بروید همه اطلاعات داخل دیگر کنترل‌های صفحه از بین می‌روند. البته این رفتار به نوعی منطقی است و از طبیعت آن ناشی می‌شود.

پ. ن.:
فهمیدن این رفتار کلی از من وقت گرفت. چون فکر می‌کردم پاک شدن اطلاعات کنترل‌ها به خاطر یک باگ جدید در برنامه است.

‫Paging بدون DataSource

بیشتر کارهای کنترل‌های ASP.NET را هم می‌توان به صورت Declartive و هم در Codebehind انجام داد. یکی از این کارها عملیات Paging در GridView است. متاسفانه یک محدودیت ظریف در حالت غیر Declarative وجود دارد که کار را برای کسانی که همه کارهای کنترل‌هایشان را در Codebehind انجام می‌دهند سخت کرده است.

اگر GridView را به یک DataSource مثل ObjectDataSource وصل کنید، آن DataSource از دو متود خاص برای بازیافت بهینه اطلاعات استفاده می‌کند. یکی متودی که برای دریافت فقط یک صفحه از اطلاعات و نه همه آنها به کار می‌رود و دیگری متودی برای دریافت تعداد کل رکوردها. اگر از DataSource استفاده نکنید باید event مربوط به PageIndexChanging را در codebehind مورد استفاده قرار دهید. کد این قسمت مشابه کد زیر خواهد بود:

protected void grdList_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
grdList.PageIndex = e.NewPageIndex;
grdList.DataSource = GetData(grdList.PageSize, e.NewPageIndex);
grdList.DataBind();
}

اما چون بدون استفاده از DataSource راهی وجود ندارد که به GridView بگوییم تعداد کل رکوردها چندتاست، پس مکانیزم Pager کار نخواهد کرد. این همان محدودیت ظریف است که در مکانیزم Paging مربوط به GridView وجود دارد. این محدودیت در این لینک MSDN هم توضیح داده شده است. برای کسب اطلاع بیشتر می‌توانید به این لینک و این لینک هم مراجعه کنید.

Castle ActiveRecord and GridView’s Paging and Sorting

Paging and sorting is a common need in ASP.NET applications. GridView itself have a default paging and sorting mechanism. Default paging has performance issues while manipulating large amounts of data. So people use a custom paging mechanism. This way they must note that only needed data must be extracted from database. For example when page 3 just shows 10 records from 21 to 30, there is no reason to load all data from database. With sorting there is two problem. First: default sorting mechanism only work with few specific data sources like Typed DataSet and not every other data sources like those come from NHibernate or Castle ActiveRecord. Second: default sorting sorts only current page not all data.

In order to have an efficient paging and proper sorting mechanism we should use custom paging and custom sorting. Scott Mitchell has a great tutorial series on paging and sorting with GridView. These series are based on an object data source of typed DataSets. So as I’m working with Castle ActiveRecord as my data access layer, I was unable to use Scott’s solution. So I decided to create my own solution using Castle Active Record based on Scott’s original solution.

Doing paging and sorting with Castle Active Record is very very easy. Because Castle ActiveRecord has an API dedicated to paging and sorting: SlicedFindAll. In my solution, firstly I have added 2 method to a typical domain class named Company, secondly notice GridView’s markup that has no codebehind at all. Notice that all my domain classes are inheriting from ActiveRecordBase:

public static Company[] FindAll(int maximumRows, int startRowIndex, string sortExpression)
{
    Order[] orders;

    if (string.IsNullOrEmpty(sortExpression))
        orders = new Order[0];
    else
    {
        orders = new Order[1];
        const string DESC = " DESC";
        if (sortExpression.EndsWith(DESC))
            orders[0] = Order.Desc(sortExpression.Replace(DESC, string.Empty));
        else
            orders[0] = Order.Asc(sortExpression);
    }

    return SlicedFindAll(startRowIndex, maximumRows, orders);
}

public static int TotalCount()
{
    return Count();
}
<asp:GridView runat="server" ID="gvCompany" AllowPaging="true" AllowSorting="true"
DataSourceID="odsCompany" DataKeyNames="Id">
<Columns>
<asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
<asp:BoundField DataField="Address" HeaderText="Address" SortExpression="Address" />
<asp:BoundField DataField="Tel" HeaderText="Tel" SortExpression="Tel" />
<asp:BoundField DataField="Field" HeaderText="Field" SortExpression="Field" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource runat="server" ID="odsCompany" TypeName="MyDomainNamespace.Company"
SelectMethod="FindAll" EnablePaging="True" SelectCountMethod="TotalCount" SortParameterName="sortExpression" />