CS前台分页功能实现

前台:浏览一个Thread的内容

http://192.168.18.153:81/forums/t/3.aspx?PageIndex=1

image001.png
其中的分页部分,是我们要研究的

image003.png 

根据网址,实际上是用了正则表达式。

查看SiteUrls.config,找到

    <url name="thread_Paged" path="t/{0}.aspx?PageIndex={1}" pattern="t/(\d+).aspx?p=(\d+)" physicalPath="##themeDir##" vanity="{2}?ThreadID=$1^PageIndex=$2" page="thread.aspx" />

<url name="thread" path="t/{0}.aspx" pattern ="t/(\d+).aspx" physicalPath="##themeDir##" vanity="{2}?ThreadID=$1" page="thread.aspx" />

这里匹配第1个,再细节,我也不考虑了,最终应该定位到

/Theme/Default/Forums/thread.aspx页面,这与你的Theme目录有关系。


void
Page_Init()

    {

        if (CurrentThread != null)

        {

            if (ChangeViewPopupMenu.GetPostViewType(CurrentThread) == PostViewType.Threaded)

                ViewContainer.Controls.Add(Page.LoadControl("thread-threadedview.ascx"));

            else

                ViewContainer.Controls.Add(Page.LoadControl("thread-flatview.ascx"));

        }

Page_Init中会根据当前的浏览类型,决定是以 树型结构还是 平板结构 显示页面

Flatview形式讨论:

再查看thread-flatview.ascx页面,这是一个用户控件

        <div class="CommonFormArea">

             <table cellpadding="0" cellspacing="0" border="0" width="100%">

                  <tr>

                       <td colspan="2" align="right" class="CommonFormField CommonPrintHidden">

                           <CSControl:Pager runat="server" id="TopPager" ShowTotalSummary="true" Tag="Div" CssClass="CommonPagingArea" align="right" />

                       </td>

                  </tr>

注意,这里有个<CSControl:Pager>用户控件,idTopPager

在页面的下端,

<CSControl:PagerGroup runat="server" Id="Pager" PagerIds="TopPager,BottomPager" />

        <CSControl:Pager runat="server" id="BottomPager" ShowTotalSummary="true" Tag="Div" CssClass="CommonPagingArea" align="right" />

这里定义了<CSControl:PagerGroup> <CSControl:Pager>,从字面意思,PageGroup控制所有的Pager

 

从官方网站,有篇文章专门讲述了一个CS Pager

 

        <CSForum:ForumPostList runat="server" ID="PostList">

            <QueryOverrides SortBy="PostDate" PagerID="Pager" />

            <HeaderTemplate><ul class="ForumPostList"></HeaderTemplate>

            <ItemTemplate>

从这里开始,主要就是显示Post列表了,用了一个Table,左边一列显示 ForumPostUserArea

右边一列分成两行,分别显示ForumPostTitleArea ForumPostBodyArea

 

如果查看CSForm:ForumPostList控件

CommunityServerForums20项目下 Controls\ForumPost\ForumPostList.cs 源程序

public override object DataSource 函数中有


                   
query.PageIndex = 0;

                    query.PageSize = ForumConfiguration.Instance().PostsPerPage;

 

                    if (this.QueryOverrides != null)

                        this.QueryOverrides.ApplyQueryOverrides(query);

 

                    if (query.PostID > 0)

                    {

                        PostSet posts = Posts.GetPosts(query);

                        _forumPosts = new List<ForumPost>();

                       

                        foreach (ForumPost p in posts.Posts)

                            _forumPosts.Add(p);

 

                        if (this.QueryOverrides != null && this.QueryOverrides.Pager != null)

                        {

                            this.QueryOverrides.Pager.PageIndex = query.PageIndex;

                            this.QueryOverrides.Pager.PageSize = query.PageSize;

                            this.QueryOverrides.Pager.TotalRecords = posts.TotalRecords;

                            this.QueryOverrides.Pager.OnPageIndexChanged += new PagerEventHandler(this.PageIndexChanged);

                            this.QueryOverrides.Pager.DataBind();

                        }

                    }

                    else if (this.QueryOverrides != null && this.QueryOverrides.Pager != null)

                    {

                        this.QueryOverrides.Pager.TotalRecords = 0;

                        this.QueryOverrides.Pager.DataBind();

                    }

当然,我们可以看到CS2007中用了泛型,这与2.1不同,2.1还是用了ArrayList,这样性能肯定能得到很大的提升,少了装箱拆箱环节。

 

PostSet posts = Posts.GetPosts(query);这一句比较重要

查看一下定义

 

        public static PostSet GetPosts(ForumPostQuery query)

        {

            return GetPosts(query.PostID, query.PageIndex, query.PageSize, query.SortBy, query.SortOrder);

        }

 

最终定位到这样一个方法

        // *********************************************************************

        //  GetPosts

        //

        /// <summary>

        /// This method returns a listing of the messages in a given thread using paging.

        /// </summary>

        /// <param name="PostID">Specifies the PostID of a post that belongs to the thread that we are

        /// interested in grabbing the messages from.</param>

        /// <returns>A PostCollection containing the posts in the thread.</returns>

        ///

        // ********************************************************************/

         public static PostSet GetPosts(int postID, int pageIndex, int pageSize, int sortBy, int sortOrder, bool includeCategories)

        {

            PostSet postSet = null;

            string key = "Forum-Posts::P:{0}-PI:{1}-PS:{2}-SB:{3}-SO:{4}-C:{5}";

            string postCollectionKey = string.Format(key,postID,pageIndex,pageSize, sortBy, sortOrder, includeCategories);

 

            CSContext csContext = CSContext.Current;

            postSet = csContext.Items[postCollectionKey] as PostSet;

 

            if(postSet == null)

                postSet = CSCache.Get(postCollectionKey) as PostSet;

           

 

            if (postSet == null) {

                // Create Instance of the CommonDataProvider

                ForumDataProvider dp = ForumDataProvider.Instance();

 

                postSet = dp.GetPosts(postID, pageIndex, pageSize, sortBy, sortOrder, CSContext.Current.User.UserID, true, includeCategories);

 

                csContext.Items[postCollectionKey] = postSet;

 

                if(pageIndex == 0)

                    CSCache.Insert(postCollectionKey,postSet,6);

            }

 

            return postSet;

        }

 

先从Content取,取不到再到Cache取,再取不到,才到数据库取。

从这里可以看出,CS充分考虑了性能,运用缓存和分页

 

ForumDataProvider dp = ForumDataProvider.Instance();

                postSet = dp.GetPosts(postID, pageIndex, pageSize, sortBy, sortOrder, CSContext.Current.User.UserID, true, includeCategories);

这一段用了一个Provider模式,具体的实现在SqlDataProvider20项目中,这里主要考虑跨数据库。CS也充分运用了设计模式,的确,老外写的程序真的不一样,不是我崇洋媚外,我是发自内心的。

 

写到这里,谈谈国货,特别是手机,国产的就不要买了。要买就买Nokia,上次移动充话费,300送个Nokia 1116,唉,我第1个手机是Nokia 3110,到现在还很好用,还给我岳父用着。如果你没钱,就用移动送的,再不行,就买个最便宜的Nokia,比什么都好,有钱,就买Nokia价位好点的,它的质量你绝对不要怀疑。

从高到低通吃,想不占领市场都难。

 

再查看GetPosts的具体实现

SqlDataProvider20项目下的 ForumsSqlDataProvider.cs类中

        #region GetPosts

        /// <summary>

        /// Returns a collection of Posts that make up a particular thread with paging

        /// </summary>

        /// <param name="postID">The ID of a Post in the thread that you are interested in retrieving.</param>

        /// <returns>A PostCollection object that contains the posts in the thread.</returns>

        ///

        public override PostSet GetPosts(int postID, int pageIndex, int pageSize, int sortBy, int sortOrder, int userID, bool returnRecordCount, bool includeCategories)

        {

 

            // Create Instance of Connection and Command Object

            //

            using( SqlConnection myConnection = GetSqlConnection() )

            {

                SqlCommand myCommand = new SqlCommand(databaseOwner + ".cs_forums_Posts_PostSet", myConnection);

                myCommand.CommandType = CommandType.StoredProcedure;

                PostSet postSet = new PostSet();

 

                // Set parameters

                //

                myCommand.Parameters.Add("@PostID", SqlDbType.Int).Value            = postID;

                myCommand.Parameters.Add("@PageIndex", SqlDbType.Int).Value         = pageIndex;

                myCommand.Parameters.Add("@PageSize", SqlDbType.Int).Value          = pageSize;

                myCommand.Parameters.Add("@SortBy", SqlDbType.Int).Value            = sortBy;

                myCommand.Parameters.Add("@SortOrder", SqlDbType.Int).Value         = sortOrder;

                myCommand.Parameters.Add("@UserID", SqlDbType.Int).Value            = userID;

                myCommand.Parameters.Add("@ReturnRecordCount", SqlDbType.Bit).Value = returnRecordCount;

                   myCommand.Parameters.Add("@IncludeCategories", SqlDbType.Bit).Value = includeCategories;

                myCommand.Parameters.Add(this.SettingsIDParameter());

 

                // Execute the command

                //

                myConnection.Open();

                using(SqlDataReader reader = myCommand.ExecuteReader(CommandBehavior.CloseConnection))

                {

 

                    // Get the results

                    //

                    IList<int> authors = new List<int>();

                    while (reader.Read())

                    {

                        authors.Add(Convert.ToInt32(reader["UserID"]));

                        postSet.Posts.Add(PopulatePostFromIDataReader(reader));

                    }

 

                    Users.AddUsersToCache(authors);

 

                       if (includeCategories)

                       {

                            reader.NextResult();

 

                            Hashtable categoryLookupTable = new Hashtable();

 

                            while(reader.Read())

                            {

                                 int cPostID = (int) reader["PostID"];

 

                                 if (categoryLookupTable[cPostID] == null)

                                 {

                                     categoryLookupTable[cPostID] = new ArrayList();

                                 }

 

                                 ((ArrayList) categoryLookupTable[cPostID]).Add(reader["Name"]);

                            }

 

                            // Map categories to the threads

                            //

                            foreach (ForumPost post in postSet.Posts)

                            {

                                 if (categoryLookupTable.ContainsKey(post.PostID))

                                     post.Categories = (string[]) ((ArrayList) categoryLookupTable[post.PostID]).ToArray(typeof(string));

                            }

                       }

 

                    // Are we expecting more results?

                    //

                    if ((returnRecordCount) && (reader.NextResult()) )

                    {

                        reader.Read();

 

                        // Read the value

                        //

                        postSet.TotalRecords = (int) reader[0];

                    }

                    reader.Close();

                }

 

                myConnection.Close();

 

                return postSet;

            }

        }

 

用了存储过程cs_forums_Posts_PostSet

查看定义

set ANSI_NULLS ON

set QUOTED_IDENTIFIER ON

GO

ALTER PROCEDURE [dbo].[cs_forums_Posts_PostSet]

(

    @PostID    int,

    @PageIndex int,

    @PageSize int,

    @SortBy int,

    @SortOrder bit,

    @UserID int,

    @ReturnRecordCount bit,

    @AllowUnapproved bit = 0,

    @SettingsID int,

    @IncludeCategories bit = 0

)

AS

SET Transaction Isolation Level Read UNCOMMITTED

BEGIN

 

DECLARE @PageLowerBound int

DECLARE @PageUpperBound int

DECLARE @ThreadID int

DECLARE @SectionID int

 

-- First set the rowcount

DECLARE @RowsToReturn int

SET @RowsToReturn = @PageSize * (@PageIndex + 1)

SET ROWCOUNT @RowsToReturn

 

-- Set the page bounds

SET @PageLowerBound = @PageSize * @PageIndex

SET @PageUpperBound = @PageLowerBound + @PageSize + 1

 

-- Get the ThreadID

SELECT

    @ThreadID = ThreadID,

    @SectionID = SectionID

FROM

    cs_Posts

WHERE

    PostID = @PostID and SettingsID = @SettingsID

 

-- Is the Forum 0 (If so this is a private message and we need to verify the user can view it

IF @SectionID = 0

BEGIN

    IF NOT EXISTS (SELECT UserID FROM cs_PrivateMessages WHERE UserID = @UserID AND ThreadID = @ThreadID AND SettingsID = @SettingsID)

       RETURN

END

 

-- Create a temp table to store the select results

CREATE TABLE #PageIndex

(

    IndexID int IDENTITY (1, 1) NOT NULL,

    PostID int

)

 

-- Sort by Post Date

IF @SortBy = 0 AND @SortOrder = 0

    INSERT INTO #PageIndex (PostID)

    SELECT PostID FROM cs_Posts (nolock) WHERE (IsApproved = 1 OR 1 = @AllowUnapproved) AND ThreadID = @ThreadID and SettingsID = @SettingsID ORDER BY PostDate

 

ELSE IF @SortBy = 0 AND @SortOrder = 1

    INSERT INTO #PageIndex (PostID)

    SELECT PostID FROM cs_Posts (nolock) WHERE (IsApproved = 1 OR 1 = @AllowUnapproved) AND ThreadID = @ThreadID and SettingsID = @SettingsID  ORDER BY PostDate DESC

 

-- Sort by Author

IF @SortBy = 1 AND @SortOrder = 0

    INSERT INTO #PageIndex (PostID)

    SELECT PostID FROM cs_Posts (nolock) WHERE (IsApproved = 1 OR 1 = @AllowUnapproved) AND ThreadID = @ThreadID and SettingsID = @SettingsID  ORDER BY UserID

 

ELSE IF @SortBy = 1 AND @SortOrder = 1

    INSERT INTO #PageIndex (PostID)

    SELECT PostID FROM cs_Posts (nolock) WHERE (IsApproved = 1 OR 1 = @AllowUnapproved) AND ThreadID = @ThreadID and SettingsID = @SettingsID  ORDER BY UserID DESC

 

-- Sort by SortOrder

IF @SortBy = 2 AND @SortOrder = 0

    INSERT INTO #PageIndex (PostID)

    SELECT PostID FROM cs_Posts (nolock) WHERE (IsApproved = 1 OR 1 = @AllowUnapproved) AND ThreadID = @ThreadID and SettingsID = @SettingsID  ORDER BY SortOrder

 

ELSE IF @SortBy = 2 AND @SortOrder = 1

    INSERT INTO #PageIndex (PostID)

    SELECT PostID FROM cs_Posts (nolock) WHERE (IsApproved = 1 OR 1 = @AllowUnapproved) AND ThreadID = @ThreadID and SettingsID = @SettingsID  ORDER BY SortOrder DESC

 

-- Select the individual posts

SELECT

    P.PostID, P.ThreadID, P.ParentID, P.PostAuthor, P.UserID, P.SectionID, P.PostLevel, P.SortOrder, P.Subject, P.PostDate, P.IsApproved,

    P.IsLocked, P.IsIndexed, P.TotalViews, P.Body, P.FormattedBody, P.IPAddress, P.PostType, P.PostMedia, P.EmoticonID, P.SettingsID, P.AggViews,

    P.PropertyNames as PostPropertyNames, P.PropertyValues as PostPropertyValues,

    P.PostConfiguration, P.UserTime, P.ApplicationPostType, P.PostName, P.PostStatus, P.SpamScore,

    P.Points as PostPoints, P.RatingSum as PostRatingSum, P.TotalRatings as PostTotalRatings,

    T.*, U.*, #PageIndex.*,

    T.IsLocked,

    T.IsSticky,

    Username = P.PostAuthor,

    ThreadStarterAuthor = T.PostAuthor,

    ThreadStartDate = T.PostDate,  

    EditNotes = (SELECT EditNotes FROM cs_PostEditNotes WHERE PostID = P.PostID),

    AttachmentFilename = ISNULL ( (SELECT [FileName] FROM cs_PostAttachments WHERE PostID = P.PostID), ''),

    Replies = 0, --not used(SELECT COUNT(P2.PostID) FROM cs_Posts P2 (nolock) WHERE P2.ParentID = P.PostID AND P2.PostLevel != 1),

    IsModerator = 0, -- not used

    HasRead = 0 -- not used

FROM

    cs_Posts P (nolock),

    cs_Threads T,

    cs_vw_Users_FullUser U,

    #PageIndex

WHERE

    P.PostID = #PageIndex.PostID AND

    P.UserID = U.UserID AND

    T.ThreadID = P.ThreadID AND

    #PageIndex.IndexID > @PageLowerBound AND

    #PageIndex.IndexID < @PageUpperBound and U.SettingsID = @SettingsID

ORDER BY

    IndexID

END

 

IF @IncludeCategories = 1

BEGIN

    SET ROWCOUNT 0

    SELECT

       Cats.[Name], jPI.PostID

    FROM

       #PageIndex jPI

       JOIN cs_Posts_InCategories PIC ON jPI.PostID = PIC.PostID

       JOIN cs_Post_Categories Cats ON PIC.CategoryID = Cats.CategoryID

    WHERE

       jPI.IndexID > @PageLowerBound

       AND jPI.IndexID < @PageUpperBound

       AND  PIC.SettingsID = @SettingsID

End

 

IF @ReturnRecordCount = 1

  SELECT count(PostID) FROM cs_Posts (nolock) WHERE (IsApproved = 1 OR 1 = @AllowUnapproved) AND ThreadID = @ThreadID  and SettingsID = @SettingsID

 

 

可以看到,SET ROWCOUNT @RowsToReturn,临时表,连接查询,分页算法,在大数据量时,应该也不错,最后一句返回记录总数。但现在SQL 2005支持RowNum,我上网看了一下,赞扬和批评的声音都有。分页确实很重要,我现在优化海学网,最先想到的就是分页和缓存。先研究一下分页。

 

上层程序通过SqlDataReader readerreader.NextResult(),封装返回的结果集和记录总数。

底层实现差不多就这样,现在再看上层展示。

public class ForumPostList : PreTemplatedListBase

public abstract class PreTemplatedWrappedRepeaterBase : WrappedRepeater

public class WrappedRepeater : Repeater, IAttributeAccessor

最终我们可以看到ForumPostList扩展了Repeater控件

PreTemplatedWrappedRepeaterBase类的

protected override void OnLoad(EventArgs e)

        {

            base.OnLoad(e);

 

            if (!_isDataBound)

                DataBind();

        }

最终会调用DataBind()函数,实现页面数据绑定

ForumPostList中改写DataSource函数。

 

<CSForum:ForumPostList runat="server" ID="PostList">

            <QueryOverrides SortBy="PostDate" PagerID="Pager" />控件中

加入了一个QueryOverrides 属性

public virtual ForumPostQuery QueryOverrides

        {

            get { return _queryOverrides; }

            set

            {

                if (_queryOverrides != null)

                    this.Controls.Remove(_queryOverrides);

 

                _queryOverrides = value;

                if (_queryOverrides != null)

                    this.Controls.Add(_queryOverrides);

            }

        }

这个属性是ForumPostQuery控件

 public class ForumPostQuery : System.Web.UI.Control

本质上这是一个传递属性字段的容器,这是我的理解

if (this.QueryOverrides != null && this.QueryOverrides.Pager != null)

                        {

                            this.QueryOverrides.Pager.PageIndex = query.PageIndex;

                            this.QueryOverrides.Pager.PageSize = query.PageSize;

                            this.QueryOverrides.Pager.TotalRecords = posts.TotalRecords;

                            this.QueryOverrides.Pager.OnPageIndexChanged += new PagerEventHandler(this.PageIndexChanged);

                            this.QueryOverrides.Pager.DataBind();

                        }

 

this.QueryOverrides.Pager.DataBind();这句话是在绑定分页控件

 

查来查去

public abstract class WrappedContentBase : Control, IAttributeAccessor, INamingContainer

抽象类中有这样一个方法,调用CreateChildControls(),这个方法会一层一层调用,最终调用

    public abstract class PreTemplatedPagerBase : WrappedContentBase, IPager, IDataItemsContainer

这个类的protected override void BindDefaultContent(Control control, IDataItemContainer dataItemContainer)方法,这样,我们的分页控件的内容就输出来了。

 

protected override void OnDataBinding(EventArgs e)

        {

            if (RecreateChildControlsOnDataBind)

            {

                this.ChildControlsCreated = true;

                this.CreateChildControls();

            }

            else

                EnsureChildControls();

 

            base.OnDataBinding(e);

        }

 

public class PagerGroup : Control, IPager

类中用了代理,

        protected override void OnInit(EventArgs e)

        {

            base.OnInit(e);

 

            this.Page.InitComplete += new EventHandler(Page_InitComplete);

        }

 

        void Page_InitComplete(object sender, EventArgs e)

        {

            string[] ids = this.PagerIds.Split(',');

            foreach (string id in ids)

            {

                IPager pager = CSControlUtility.Instance().FindControl(this, id) as IPager;

                if (pager != null)

                    pager.OnPageIndexChanged += new PagerEventHandler(PagerGroup_OnPageIndexChanged);

            }

        }

 

当里面的任何一个分页控制,发生页数更改时,就会调用这个Group类,Group再修改所有分页控件的样式。因为分页是采用超链接,我想这个代理仅会发生在页面载入时。

 

这里是“窥一斑而见全豹”,还有很多要学的,再看看吧。

 

再来分析后台分页功能,后台用了

<add tagPrefix="CA" namespace="ComponentArt.Web.UI" assembly="ComponentArt.Web.UI"/> Grid控件。

 image005.png

其实这里没什么好说的,取数据还是存储过程取,表现用了ComponentArt控件,就方便多了,实现的原理和ComponentArt自带的例子里的一个Manual Page一样,就是手工分页。

 

原文地址:https://www.cnblogs.com/huang/p/989692.html